Почему оператор ++ возвращает неконстантное значение? - PullRequest
23 голосов
/ 10 июня 2011

Я прочитал «Эффективное С ++», 3-е издание, написанное Скоттом Мейерсом.

В пункте 3 книги «Использовать const всегда, когда это возможно» говорится, что если мы хотим предотвратить случайное присвоение r-значений возвращаемому значению функции, тип возврата должен быть const.

Например, функция приращения для iterator:

const iterator iterator::operator++(int) {
    ...
}

Затем предотвращаются некоторые несчастные случаи.

iterator it; 

// error in the following, same as primitive pointer
// I wanted to compare iterators
if (it++ = iterator()) {
  ...
}

Однако итераторы, такие как std::vector::iterator в GCC, не возвращают const значений.

vector<int> v;
v.begin()++ = v.begin();   // pass compiler check

Есть ли причины для этого?

Ответы [ 3 ]

11 голосов
/ 10 июня 2011

Я почти уверен, что это потому, что это может привести к хаосу с ссылками на rvalue и любым видом decltype.Хотя эти функции отсутствовали в C ++ 03, известно, что они появятся.

Что еще более важно, я не верю, что любая стандартная функция возвращает константные значения, это, вероятно, то, что не рассматривалосьдо тех пор, пока стандарт не был опубликован.Кроме того, постоянные значения, как правило, не считаются правильным решением ™.Не все случаи использования неконстантных функций-членов недопустимы, и возвращение константных значений полностью предотвращает их.

Например,

auto it = ++vec.begin();

является совершенно допустимой и, действительно, допустимой семантикой, еслине совсем желательно.Рассмотрим мой класс, который предлагает цепочки методов.

class ILikeMethodChains {
public:
    int i;
    ILikeMethodChains& SetSomeInt(int param) {
        i = param;
        return *this;
    }
};
ILikeMethodChains func() { ... }
ILikeMethodChains var = func().SetSomeInt(1);

Следует ли это запретить только потому, что, может быть, иногда мы можем вызвать функцию, которая не имеет смысла?Нет, конечно нет.Или как насчет «swaptimization»?

std::string func() { return "Hello World!"; }
std::string s;
func().swap(s);

Это было бы недопустимо, если бы func() произвел выражение const - но это совершенно правильно и действительно, предполагая, что реализация std::string не выделяет никакой памяти вконструктор по умолчанию, быстрый и разборчивый / читабельный.

Следует понимать, что правила C ++ 03 rvalue / lvalue, честно говоря, просто не имеют смысла.Они, фактически, только частично испечены, и минимум, необходимый, чтобы запретить некоторые вопиющие ошибки, допуская некоторые возможные права.Правила C ++ 0x rvalue намного разумнее и намного более совершенные complete .

1 голос
/ 10 июня 2011

Если it неконстантно, я ожидаю, что *(++it) даст мне изменяемый доступ к тому, что оно представляет.

Однако разыменование итератора const дает только неизменяемый доступ к объекту, который он представляет. [ edit: нет, это тоже неправильно, Я действительно сдаюсь сейчас!]

Это единственная причина, о которой я могу думать.

Как вы правильно заметили, следующее неверно, потому что ++ в примитиве дает значение (которого не может быть в LHS):

int* p = 0;
(p++)++;

Так что здесь, кажется, есть что-то вроде несоответствия в языке.

0 голосов
/ 10 июня 2011

РЕДАКТИРОВАТЬ : Это не совсем ответ на вопрос, как указано в комментариях.Я просто оставлю этот пост здесь на тот случай, если он все равно будет полезен ...

Я думаю, что это в значительной степени вопрос унификации синтаксиса в сторону более удобного интерфейса.Когда вы предоставляете такие функции-члены без разграничения имени и позволяете только механизму разрешения перегрузки определять правильную версию, которую вы предотвращаете (или, по крайней мере, пытаетесь) программисту делать const связанных с этим забот.противоречивый, в частности приведенный твой пример.Но если вы думаете о большинстве случаев использования, это имеет смысл.Возьмите алгоритм STL, такой как std::equal.Независимо от того, является ли ваш контейнер постоянным или нет, вы всегда можете кодировать что-то вроде bool e = std::equal(c.begin(), c.end(), c2.begin()), не думая о правильной версии начала и конца.

Это общий подход в STL.Вспомните operator[] ... Учитывая, что контейнеры должны использоваться с алгоритмами, это правдоподобно.Хотя также заметно, что в некоторых случаях вам все же может потребоваться определить итератор с соответствующей версией (iterator или const_iterator).

Что ж, это только то, что приходит мне в голову сейчас.Я не уверен, насколько это убедительно ...

Примечание : правильный способ использовать постоянные итераторы - через const_iterator typedef.

...