Допустимо ли такое дублирование?

Модераторы: Hawk, Romeo, Absurd, DeeJayC, WinMain

Аватара пользователя
Сионист
Сообщения: 1211
Зарегистрирован: 31 мар 2014, 06:18

Есть класс контейнера, а в нём класс иттератора. Допустимо ли прописать оператор приведения иттератора к bool, который будет возвращать true, если перебор элементов не завершён, и false, если уже завершён? То есть допустимо ли сделать так, чтоб условие

Код: Выделить всё

(bool)Itterator
было эквивалентно условию

Код: Выделить всё

Itterator!=Contaner.End()
, где Contaner - экземпляр контейнера, а Itterator - экземпляр его иттератора?
Писать можно на чём угодно, но зачем же так себя ограничивать? Пиши на c.
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Не каждый итератор может сам по себе определить, что перебор контейнера завершён. Итератор списка, как в твоём случае, может. А вот, например итератор вектора не знает, принадлежит ли следующая лежащая в памяти ячейка контейнеру или нет, поэтому без контейнера это определить не получится.

Я к чему веду. Если введёшь такую поддержку, то для одних итераторов такую проверку можно будет делать, а для других нельзя. Это плохая идея с точки зрения универсальности кода. Если интерфейс универсален, то легко можно будет поменять тип контейнера, не трогая остальной код. Именно поэтому в текущей реализации STL проверку можно делать только через контейнер.
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
Сионист
Сообщения: 1211
Зарегистрирован: 31 мар 2014, 06:18

Не каждый итератор может сам по себе определить, что перебор контейнера завершён. Итератор списка, как в твоём случае, может. А вот, например итератор вектора не знает, принадлежит ли следующая лежащая в памяти ячейка контейнеру или нет, поэтому без контейнера это определить не получится.
Не понял, поясните. В чём собственно проблема?

Код: Выделить всё

class TArray
{
 private:
  int *Items;
  size_t Count;
 public:
  Itterator Begin()
  {
   return Itterator(Items, Items+Count-1);
  }
  class Itterator
  {
   friend class TArray
  private:
   int *Last;
   int *Current;
   Itterator (const int *Current, const int *Last)
   {
    this->Current=Current;
    this->Last=Last;
   }
  public:
   Itterator ()
   {
    Current=nullptr;
    Last=nullptr;
   }
   Itterator oparator ++ ()
   {
    if ((Current!=nullptr)&&(Last!=nullptr))
    {
     ++Current;
     if (Current>Last)
     {
      Current=nullptr;
     }
    }
   }
 };
Или в этом решении есть подводные камни? Какие?
Писать можно на чём угодно, но зачем же так себя ограничивать? Пиши на c.
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Проблема в том, что итератор, по факту, должен быть некой обёрткой над поинтером, который указывает на данные. Более того, он должен притворяться этим поинтером, то есть иметь такой же интерфейс. Любые усложнения нарушают концепцию универсальности, а так же приводят к мемори/перфоменс оверхэду.

Зачем нужна универсальность интерфейса итератора? Она позволяет обобщённым алгоритмам из <algorithm> работать одинаково как с итераторами, так и с обычными указателями, которые конечно же не имеют никаких дополнительных знаний о контейнерах.
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Аватара пользователя
Сионист
Сообщения: 1211
Зарегистрирован: 31 мар 2014, 06:18

Romeo писал(а):Проблема в том, что итератор по факту должен быть некой обёрткой над поинтером, который указывает на данные и, более того, притворятся эти поинтером, то есть иметь такой же интерфейс.
И в чём проблема перегрузить оператор "унарный *"?
Любые усложнения нарушают концепцию универсальности,
И каким же образом могут быть универсальны внутренности иттератора? Их классы не просто так являются членами классов контейнеров вместо единого класса иттератора "вперёд" и одного реверсивного.
Зачем нужна универсальность интерфейса итератора?
А это то здесь при чём?
Она позволяет обобщённым алгоритмам из <algorithm> работать одинаково как с итераторами, так и с обычными указателями, которые конечно же не имеют никаких дополнительных знаний о контейнерах.
Ну ка переберите список указателем, юзая простой инкремент вместо p=p->Next. Они ведь не знают не только о конце контейнера, но и о фактическом размещении следующего элемента.
Писать можно на чём угодно, но зачем же так себя ограничивать? Пиши на c.
Absurd
Сообщения: 1228
Зарегистрирован: 26 фев 2004, 13:24
Откуда: Pietari, Venäjä
Контактная информация:

Итератор это разновидность смарт-указателя. У смарт-указателей operator bool() обычно перегружают, чтобы работало такое:

Код: Выделить всё

std::unique_ptr<Foo> p = getPooPtr();
if (p) {
 // p is valid
}
2B OR NOT(2B) = FF
Аватара пользователя
Сионист
Сообщения: 1211
Зарегистрирован: 31 мар 2014, 06:18

Ну правильно. Пока итератор указывает на элемент, он валиден, а как только перебор завершён, уже не валиден. Но итератор есть указатель, учитывающий структуру контейнера и фактическое размещение в памяти следующего элемента.
Писать можно на чём угодно, но зачем же так себя ограничивать? Пиши на c.
Absurd
Сообщения: 1228
Зарегистрирован: 26 фев 2004, 13:24
Откуда: Pietari, Venäjä
Контактная информация:

Но итератор есть указатель, учитывающий структуру контейнера и фактическое размещение в памяти следующего элемента.
Смарт-указатель может учитывать все что угодно. std::unique_ptr, например, позволяет учитывать заданные пользователем способы удаления объекта.
2B OR NOT(2B) = FF
Аватара пользователя
Romeo
Сообщения: 3126
Зарегистрирован: 02 мар 2004, 17:25
Откуда: Крым, Севастополь
Контактная информация:

Absurd писал(а):Итератор это разновидность смарт-указателя.
В корне не согласен. Итераторы и смарт-поинтеры похожи только тем, что прячут в себе указатель. Но по ни по функциональному предназначению, ни по имплементационым требованиям они вообще не сравнимы.

Итераторы используются лишь как средство для перебора элементов контейнера. То есть у итератора должна быть возможность перехода к предыдущему/следующему элементу (для random acceess ещё и средства произвольного доступа) и возможность получить данные, на которые итератор указывает. Если отбросить пару дополнительных примочек (таких, как сравнение итераторов), то на этом их функциональность заканчивается. Итераторы никак не владеют данными, на которые указывают и, тем более, не могут их удалять!

Смарт-поинтеры предназначены для хранения указателя на память и управления этой памятью. Здесь идёт акцент на владение (с помощью разных стратегий - экслюзивное, разделяемое, слабое) и на своевременное удаление. Но, в противовес итераторам, смарт-поинтеры не имеют возможность перейти к предыдущему/следующему указателю.
Absurd писал(а):У смарт-указателей operator bool() обычно перегружают, чтобы работало такое.
В текущей реализации STL у итератора нет оператора bool. И, кстати, это объясняется достаточно просто. Если для итераторов одних контейнеров, таких, как список, нулевое значение внутреннего указателя может сигнализировать об окончании перебора, то для других, скажем, вектора, окончание перебора вообще не может быть определено без дополнительной информации, так как итерирование, по факту, идёт по диапазону адресов, и где заканчивается этот диапазон "знает" только контейнер.
Сионист писал(а):И каким же образом могут быть универсальны внутренности иттератора? Их классы не просто так являются членами классов контейнеров вместо единого класса иттератора "вперёд" и одного реверсивного.
Речь идёт об универсальности интерфейса, а не внутренностей. Дело в том, что все алгоритмы реализованы в виде внешних шаблонных функций, так что они не привязаны к типу итератора. Можно передать как обычные поинтеры, так и объекты своего класса. Главное, чтоб был доступен весь набор необходимых операторов, чтобы компилятор смог сгенерировать инстанс функции для этого типа.
Сионист писал(а):Ну-ка переберите список указателем, юзая простой инкремент вместо p=p->Next. Они ведь не знают не только о конце контейнера, но и о фактическом размещении следующего элемента.
Ну почему ты любую идею пытаешь извратить? Смотри, давай по порядку.

В языке C был один единственный встроенный контейнер. Знаешь такой? Это C-массив. Элементы массива можно было перебрать, используя указатель. Например вот так:

Код: Выделить всё

const int N = 3;
int arr[N] = {1, 2, 3};

...

int* begin = arr;
int* end = arr + N;

for (int* p = begin; p != end; ++p)
{
   printf("%d ", *p);
}
Эта идея достаточно проста, поэтому, надеюсь, понята.

Создавая язык С++, разработчики сделали следующий шаг и абстрагировали предыдущий подход. Они создали билиотеку STL, наполнили её классами-контейнерами и ввели понятие итератора.

- А что же такое итератор, - спросили их программисты?

- Итератор - это такой объект, которым можно перебрать элементы контейнера, - ответили разработчики. - Причём у этого итератора вы можете использовать ровно такой же набор операторов, какой использовали у поинтера, когда перебирали С-массив. Этим мы добились того, что даже для С-программистов код остался прозрачным и ожидаемым. А ещё получили возможность, одинаково хорошо применять все фишки из нашего нового <algorithm> как для встроенных итераторов (читайте "поинтеров"), так и для пользовательских. Единообразность и ожидаемость - наше всё.

И так, ещё раз от просто к сложному. С-массив -> поинтер. Пользовательский контейнер -> итератор. Идея понятна?

Сионист, ещё раз хочу подчеркнуть, что всё, что я написал выше, касается исключительно STL. Так что, если ты собираешься делать свой итератор, ты волен заимплементить его так, как посчитаешь нужным. Я лишь высказываю резонные мысли и ставлю в пример достаточно продуманную готовую библиотеку, давая тем самым тебе пищу для размышлений.
Entites should not be multiplied beyond necessity @ William Occam
---
Для выделения С++ кода используйте конструкцию [ code=cpp ] Код [ /code ] (без пробелов)
---
Сообщение "Спасибо" малоинформативно. Благодарность правильнее высказать, воспользовавшись кнопкой "Reputation" в виде звёздочки, расположенной в левом нижнем углу рамки сообщения.
Absurd
Сообщения: 1228
Зарегистрирован: 26 фев 2004, 13:24
Откуда: Pietari, Venäjä
Контактная информация:

Итераторы и смарт-поинтеры похожи только тем, что прячут в себе указатель. Но по ни по функциональному предназначению, ни по имплементационым требованиям они вообще не сравнимы.
Ну не знаю. По моему все типы с перегруженными * и -> входят в множество обобщенных указателей. А обобщенные указатели это тоже самое что и смарт-указатели.
В текущей реализации STL у итератора нет оператора bool.
Ну это понятно что каноничный способ сравнения это сравнение с end(). Но ничто не мешает этот bool реализовать при желании.
2B OR NOT(2B) = FF
Ответить