Почему я могу обновить переменные-члены в константной функции-члене? - PullRequest
1 голос
/ 23 апреля 2020

Я пытаюсь реализовать связанный список, аналогичный тому, как он может быть реализован в STL. При реализации итератора я создал несколько константных функций-членов (чтобы пользователь мог использовать константный итератор) и заметил, что смог обновить переменные-члены без получения ошибки компилятора. В коде используются шаблоны, но я тестировал его, вызывая функцию, которая использует begin () и со списком констант, поэтому я знаю, что функции шаблона, которые изменяют переменные-члены, были сгенерированы компилятором. Кто-нибудь знает, почему это работает? Рассматриваемая функция является константной версией оператора ++.

Вот версия моей программы с несущественными подробностями.

template<typename E>
struct Link {
    E val {};
    Link* next = nullptr;
    Link* prev = nullptr;
};

template<typename E>
struct List {   
    struct Iterator {
        Iterator(Link<E>* c) : curr{c} { }

        Iterator& operator++();
        const Iterator& operator++() const;

        const E& operator*() const {return curr->val;}
        E& operator*() {return curr->val;}

        // ...
    private:
        Link<E>* curr;
    };

    // Constructors, etc ...
    // Operations ...

    E& front() {return head->val;}
    const E& front() const {return head->val;}

    Iterator begin() {return Iterator{head};}
    const Iterator begin() const {return Iterator{head};}
    // Other iterator stuff ...

private:
    Link<E>* head;
    Link<E>* tail;
    int sz;
};

/*---------------------------------------------*/

template<typename E>
typename List<E>::Iterator& List<E>::Iterator::operator++() {
    curr = curr->next;
    return *this;
}

template<typename E>
const typename List<E>::Iterator& 
        List<E>::Iterator::operator++() const
{
    curr = curr->next;
    return *this;
}

Я думаю, что концептуально имеет смысл сделать константную версию оператора ++, даже если он изменяет переменные-члены. Константный итератор фактически ссылается на содержимое указателя Link, являющегося const, именно поэтому он возвращает const E & в операторе разыменования. Таким образом, с помощью константного итератора вы никогда не сможете обновить содержимое итератора.

Дайте мне знать, если я что-то включу в фрагмент кода, спасибо!

1 Ответ

4 голосов
/ 23 апреля 2020

Шаблонные функции фактически не проверяются на наличие ошибок, пока они не будут созданы. Если вы не позвоните им, они просто останутся там незамеченными, бомбы ждут, чтобы взлететь go. Вы получите ошибку компилятора, как только добавите вызов к Iterator::operator++() const.

Например, я добавил:

int main() {
    List<int> list;
    const List<int>::Iterator iter = list.begin();
    ++iter;
}

И теперь Clang жалуется:

main.cpp:52:10: error: cannot assign to non-static data member within const
      member function 'operator++'
    curr = curr->next;
    ~~~~ ^
main.cpp:61:3: note: in instantiation of member function
      'List<int>::Iterator::operator++' requested here
  ++iter;
  ^
main.cpp:14:25: note: member function 'List<int>::Iterator::operator++' is
      declared const here
        const Iterator& operator++() const;
                        ^

( Repl )


Я думаю, что концептуально имеет смысл сделать константную версию оператора ++, даже если он изменяет переменные-члены. Константный итератор фактически ссылается на содержимое указателя Link, являющегося const, именно поэтому он возвращает const E & в операторе разыменования. Таким образом, с помощью константного итератора вы никогда не сможете обновить содержимое итератора.

Итератор const не должен быть изменяемым и не иметь оператора ++. STL на самом деле имеет отдельные типы iterator и const_iterator. const_iterator воплощает концепцию, которую вы описываете: итератор сам изменчив, но он указывает на const.

Я рекомендую вам последовать его примеру и создать отдельный класс ConstIterator.

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