Страницы

Поиск по вопросам

воскресенье, 29 декабря 2019 г.

Корректно ли сравнивать итераторы разных типов?

#cpp #cpp11 #stl #language_lawyer


Предыстория: на моей машине установлена ОС Ubuntu, версия gcc - 5.4.0. Я свободно
могу вызывать метод erase (для переменной типа std::vector, например) передавая ей
параметром const_iterator на элемент, подлежащий удалению.
На другой машине стоит та же ОС Ubuntu, но стоит gcc версии 4.8.4 - и, как я понял,
в этой версии (хотя она и поддерживает компиляцию с флагом std=c++11) метод erase принимает
только iterator.

В общем, код выглядит так: 

void SomeoneClass::method( ... )
{
    Subscribers::const_iterator pos = anotherMethod( ... );
    if( pos != m_subscribers.end() )
    {
        m_subscribers.erase( pos );
    }
}


Я решил добавить проверку на используемую версиюю gcc, вроде 

#if defined( __GNUC__ ) && ( __GNUC__ < 5 )
    // тут получаем номер элемента через pos и т.д
#else
    Subscribers::const_iterator pos = anotherMethod( ... );
    if( pos != m_subscribers.end() )
    {
        m_subscribers.erase( pos );
    }
#endif


Тут возникли вопросы:


Вправе ли я делать подобное сравнение: const_iterator pos != container.end() ?
Как правильно получить номер элемента через const_iterator, а затем по этому номеру
получить итератор ? (напомню, gcc 4.8) Что-то вроде этого:




Subscribers::difference_type itemPos = pos - m_subscribers.begin();
Subscribers::iterator it = m_subscribers.begin() + itemPos;




UPD: прошу прощения. Рассматриваем не std::vector, а std::list
    


Ответы

Ответ 1



В стандартной библиотеке C++ присутствует возможность получения объекта const_iterator из iterator. Вероятнее всего (не силён в деталях реализации) используется примерно такой конструктор const_iterator(iterator) {...} без указания explicit, но заметьте: обратное преобразование (iterator(const_iterator)) недопустимо. Таким образом в коде const_iterator pos != container.end() итератор end() неявно преобразуется в константный итератор. Пример программы: #include #include using namespace std; int main(int /*argc*/, char** /*argv*/) { vector v; v.resize(5); vector::iterator it = v.begin(); // получаем итератор vector::const_iterator ct = v.begin(); // получаем константный итератор cout << "it == ct ? " << (it == ct ? "True" : "False") << endl; // сравниваем константный итератор с неконстантным (it будет приведён в const_iterator) vector::const_iterator ct2 = it; // получаем константный итератор из неконстантного cout << "ct2 == begin() ? " << (ct2 == v.begin() ? "True" : "False") << endl; // сравниваем константный итератор с неконстантным (begin() будет приведён в const_iterator) cout << "it == const begin() ? " << (it == const_cast&>(v).begin() ? "True" : "False") << endl; // сравниваем константный итератор с неконстантным (it будет приведён в const_iterator) // а вот так сделать уже не получится: // vector::iterator it2 = ct; // попытаемся получить неконстантный итератор из константного return 0; } UPD: В стандарт c++11 контейнерам были добавлены функции для получения const_iterator без дополнительных преобразований: // замените container на нужный вам конкретный тип: const_iterator container.cbegin() const_iterator container.cend()

Ответ 2



Насколько мне известно, в C++ 2003 вы могли с методом erase использовать лишь неконстантные итераторы. В C++ 2011 это положение изменили, и стало возможным вызывать метод erase с константными итераторами. Сама функция-член класса erase не является константной, а потому не важно, какой итератор ей передали. Итератор лишь задает позицию того элемента, который должен быть удален. Сам элемент не изменяется, используя этот итератор. Сравните. Вы можете динамически создавать константный объект. Например, const int *pi = new int( 10 ); Но, как вы его создали, так вы его можете и удалить, используя тот же самый указатель с квалификатором const. delete pi; Другое дело, если бы вы попытались изменить созданный объект, используя этот указатель, как, например, *pi = 20; то компилятор бы выдал сообщение об ошибке, так как нельзя менять константный объект. Вы можете сравнивать константный и не константный итераторы, так как не константный итератор неявно может быть преобразован к константному итератору. Что касается вопроса Как правильно получить номер элемента через const_iterator, а затем по этому номеру получить итератор ? (напомню, gcc 4.8) Что-то вроде этого: то имеется стандартная функция std::distance объявленная в заголовке , которая позволяет вычислить позицию, имея итератор. Например, если вы имеете список std::list lst { 1, 2, 3, 4, 5 }; и нашли элемент со значением 3, используя стандартный алгоритм std::find auto it = std::find( lst.begin(), lst.end(), 3 ); то вы может определить позицию найденного элемента в списке следующим образом auto pos = std::distance( lst.begin(), it ); Однако для контейнеров, которые не имеют итераторов произвольного доступа, как это имеет место со стандартным контейнером std::list, данная операция не эффективна. Просто функция последовательно пройдется по всему списку, пока не дойдет до заданного итератора. Это может выглядеть следуюшим образом std::list::size_type pos = 0; for ( auto current = lst.begin(); current != it; ++current ) { ++pos; } Имея начальный итератор и заданную позицию, вы можете получить соответствующий позиции итератор с помощью функций std::advance или функций std::next и std::prev, которые также объявлены в заголовке . Например, имея начальный итератор и позицию pos, вычисленную ранее, вы можете получить итератор it, следующим образом auto it = std::next( lst.begin(), pos ); Однако вы должны быть осторожны, вызывая эту функцию, так как она не проверяет, является ли значение переменной pos корректным, и не будет ли выход за пределы допустимого диапазона для итераторв для заданного контейнера.

Комментариев нет:

Отправить комментарий