Как проверить, инициализирован ли итератор? - PullRequest
18 голосов
/ 05 августа 2011

Если я использую конструктор по умолчанию для итератора, как проверить, был ли он назначен позже?

Для указателей я мог бы сделать это:

int *p = NULL;
/// some code
if ( NULL == p ) {
  // do stuff
}

Как мне сделать выше для итераторов? Возможно ли это вообще?

#include <iostream>
#include <list>

int main ()
{
    std::list<int>::iterator it;

  if ( NULL == it ) // this fails
  {
      std::cout<<"do stuff" << std::endl;
  }
}

Ответы [ 11 ]

17 голосов
/ 05 августа 2011

Мне удалось найти это в текущем стандарте (c ++ 03).24.1 p 5 сообщает:

Так же, как обычный указатель на массив гарантирует, что существует значение указателя, указывающее за последним элементом массива, так и для любого типа итератора есть значение итератора, которое указываетмимо последнего элемента соответствующего контейнера.Эти значения называются прошлыми значениями.Значения итератора i, для которых определено выражение i, называются разыменованными.Библиотека никогда не предполагает, что последние значения являются разыменованными. Итераторы также могут иметь единичные значения, которые не связаны ни с одним контейнером. [Пример: после объявления неинициализированного указателя x (как в случае int x;), x всегда должен иметь значениеединственное значение указателя.] Результаты большинства выражений не определены для единичных значений;единственное исключение - присвоение не единственного значения итератору, который содержит единственное значение. В этом случае единственное значение перезаписывается так же, как и любое другое значение.Разыменовываемые значения всегда не единственные.

(выделение мое)

Таким образом, ответ: нет, это невозможно.

3 голосов
/ 05 августа 2011

Большинство итераторов не имеют глобальных специальных значений так же, как все указатели могут быть NULL.Однако, как правило, вы будете работать с определенными контейнерами, и если у вас будет один итератор для каждого контейнера, то вы можете использовать end() в качестве значения часового:

std::list<int> mylist;
std::list<int>::iterator it = mylist.end();

/* do stuff */

if (it == mylist.end()) { ... }

Я не уверен, что вставка/ delete делает недействительным итератор end(), поэтому, если вы планируете модифицировать свой контейнер, возможно, также сохраните копию исходного конца:

std::list<int>::iterator end = mylist.end(), it = end;

if (it == end) { ... }

Хотя, опять же, я на самом деле не уверенесли он четко определен для сравнения двух недопустимых итераторов (в случае, если два действительно станут недействительными).

1 голос
/ 02 сентября 2015

Поскольку для итераторов нет значения по умолчанию (например, для указателей есть NULL), в ситуации, когда мне нужно общее значение по умолчанию для Object::iterator (до создания какого-либо фактического объекта), я создаю фиктивную статическую переменную ииспользуйте его ::end() по умолчанию.

Обновление: это работает только для Release, потому что в DEBUG (или с _HAS_ITERATOR_DEBUGGING=1) операторы сравнения проверяют, указывают ли оба итератора на один и тот же объект / контейнер.

Например, для vector<int> я бы сделал:

class A
{
public :
    A() :  myIterator1(dummyVector.end()), myIterator2(dummyVector.end()) {}
    // needed iterators
    vector<int>::iterator myIterator1;
    vector<int>::iterator myIterator2;

    static const vector<int> dummyVector;
}

#define  IT_NULL A::dummyObject.end()

void maint() {
    A::dummyObject = vector<int>(); // initialize the Null iterator

    A a;
    if(a.myIterator1 == IT_NULL) cout << "Iterator not yet initialized";
}
1 голос
/ 05 августа 2011

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

1 голос
/ 05 августа 2011

В C ++ неинициализированные локальные переменные могут иметь любое значение, т. Е. Оно содержит просто мусор. Это означает, что вы не можете проверить его по какому-то четко определенному значению, чтобы определить, неинициализирована ли переменная.

Мало того, что если переменная не инициализирована и вы пишете это:

if ( NULL == it ) // this fails

тогда он вызывает неопределенное поведение.

0 голосов
/ 09 июня 2016

Я использовал следующее решение:

const MyList_t::const_iterator NullIterator(NULL);
const_iterator MyList_t::MyIterator;

Тогда возможна проверка:

if (NullIterator != MyIterator) {}
0 голосов
/ 07 сентября 2011

Насколько я знаю, вы всегда должны инициализировать свои итераторы, и самый простой способ - сделать их равными 'container'.end () В некоторых случаях это выглядит как работа, у нас были некоторые проблемы с кодом, который работал с VC6 и перестал работать с VC2010. Посмотрите на этот пример, скомпилированный с g ++, где он работает для вектора, но не для карты:

# Test iterator init, compile with: g++ test-iterator.cpp -o test-iterator
#include <iostream>
#include <vector>
#include <map>

int main()
{
    std::vector<int> vec;
    std::vector<int>::iterator it;

    if (it != vec.end())
    {
            std::cout << "vector inside!" << std::endl;
    }
    else
    {
            std::cout << "vector outside!" << std::endl;
    }

    std::map<int, int> mp;
    std::map<int, int>::iterator itMap;

    if (itMap != mp.end())
    {
            std::cout << "map inside!" << std::endl;
    }
    else
    {
            std::cout << "map outside!" << std::endl;
    }

    return 0;
}
0 голосов
/ 05 августа 2011

Лучший способ сделать это, как я думаю, это что-то вроде

#include <utility>
#include <map>
#include <typeinfo>
#include <string>



namespace nulliterators {

typedef std::map<std::string, void*> nullcntT;
nullcntT nullcontainers;

template<class containerT>
typename containerT::iterator iterator() {
  containerT* newcnt = new containerT();
  std::string cnttypename = typeid(*newcnt).name();
  nullcntT::iterator i = nullcontainers.find(cnttypename);
  if (i==nullcontainers.end()) {
    nullcontainers.insert(make_pair(cnttypename, newcnt));
    return newcnt->end();
   }else{
    delete newcnt;
    return (static_cast<containerT*>(i->second))->end();
  }
}

}
template<class containerT>
typename containerT::iterator nulliterator() { return nulliterators::iterator<containerT>(); }


#include <list>
#include <iostream>


int main(){

  std::list<int>::iterator nullinitized = nulliterator< std::list<int> >();
  std::list<int> somelist;
  std::list<int>::iterator initialized = somelist.end();

  if (nullinitized == nulliterator< std::list<int> >())
    std::cout << "nullinitized == nulliterator< std::list<int> >()\n";  //true
   else
    std::cout << "nullinitized != nulliterator< std::list<int> >()\n";

  if (initialized == nulliterator< std::list<int> >())
    std::cout << "initialized == nulliterator< std::list<int> >()\n";
   else
    std::cout << "initialized != nulliterator< std::list<int> >()\n";  //true

  return 0;
}

но это не совсем безопасное решение (потому что оно опирается на неконстантные глобальные контейнеры в nullcontainers).

0 голосов
/ 05 августа 2011
if(std::list<int>::iterator() == it)

Но я подозреваю ... возможно, что действительный итератор может пройти сравнение.Лучше избегать таких ситуаций.Если невозможно сохранить итератор указателем.

 std::auto_ptr<std::list<int>::iterator> it;
0 голосов
/ 05 августа 2011

Может быть, вы всегда должны назначать предопределенное значение, например, NULL, после создания итератора.Позже вы можете легко проверить против NULL.Это сделает ваш код более переносимым, поскольку вы не будете зависеть от того, какие начальные значения неинициализированные переменные принимают в начале.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...