Должен ли я вернуть const объекты?

В Effective C++ Пункт 03, Используйте const всякий раз, когда это возможно.

class Bigint
{
  int _data[MAXLEN];
  //...
public:
  int& operator[](const int index) { return _data[index]; }
  const int operator[](const int index) const { return _data[index]; }
  //...
};

const int operator[] действительно имеет значение от int& operator[],

Но что насчет:

int foo() { }

а также

const int foo() { }

Похоже, что они одинаковы.

Мой вопрос, почему мы используем const int operator[](const int index) const вместо int operator[](const int index) const?

12 ответов

Решение

Спецификаторы cv верхнего уровня для типов возвращаемых данных, не относящихся к типу классов, игнорируются. Это означает, что даже если вы напишите:

int const foo();

Тип возврата int, Если тип возвращаемого значения является ссылкой, конечно, const больше не верхний уровень, а различие между:

int& operator[]( int index );

а также

int const& operator[]( int index ) const;

является значительным. (Обратите внимание, что в объявлениях функций, как и выше, любые cv-квалификаторы верхнего уровня также игнорируются.)

Различие также имеет отношение к возвращаемым значениям типа класса: если вы возвращаете T constтогда вызывающая сторона не может вызывать неконстантные функции для возвращаемого значения, например:

class Test
{
public:
    void f();
    void g() const;
};

Test ff();
Test const gg();

ff().f();             //  legal
ff().g();             //  legal
gg().f();             //  **illegal**
gg().g();             //  legal

Вы должны четко различать использование const, применяемого к возвращаемым значениям, параметрам и самой функции.

Возвращаемые значения

  • Если функция возвращает значение, const не имеет значения, так как возвращается копия объекта. Однако это будет иметь значение в C++11 с учетом семантики перемещения.
  • Это также никогда не имеет значения для базовых типов, так как они всегда копируются.

Рассматривать const std::string SomeMethod() const, Это не позволит (std::string&&) Используемая функция, так как она ожидает неконстантное значение. Другими словами, возвращаемая строка всегда будет скопирована.

  • Если функция возвращается по ссылке, const защищает возвращаемый объект от изменения.

параметры

  • Если вы передаете параметр по значению, const предотвращает изменение данного значения функцией в функции. Исходные данные из параметра не могут быть изменены в любом случае, так как у вас есть только копия.
  • Обратите внимание, что поскольку копия всегда создается, const имеет значение только для тела функции, поэтому проверяется только в определении функции, а не в объявлении (интерфейсе).
  • Если вы передаете параметр по ссылке, применяется то же правило, что и в возвращаемых значениях

Сама функция

  • Если функция имеет const в конце концов, он может запустить только другой const функции, и не может изменить или разрешить изменение данных класса. Таким образом, если он возвращается по ссылке, возвращаемая ссылка должна быть константной. Только const-функции могут быть вызваны для объекта или ссылки на объект, который является самим const. Также изменяемые поля могут быть изменены.
  • Поведение, созданное компилятором, меняется this ссылка на T const*, Функция всегда может const_castthis, но, конечно, это не должно быть сделано и считается небезопасным.
  • Разумеется, разумно использовать этот спецификатор только в методах класса; Глобальные функции с константой в конце вызовут ошибку компиляции.

Заключение

Если ваш метод не изменяет и никогда не будет изменять переменные класса, пометьте его как const и убедитесь, что он соответствует всем необходимым критериям. Это позволит писать более чистый код, сохраняя его константно-корректным. Тем не менее, положить const везде, не думая об этом, конечно, не путь.

Существует мало ценности в добавлении const квалификации к не ссылочным / не указательным значениям, и нет смысла добавлять его во встроенные модули.

В случае пользовательских типов, const квалификация не позволит вызывающим constфункция-член для возвращаемого объекта. Например, учитывая

const std::string foo();
      std::string bar();

затем

foo().resize(42);

будет запрещено, в то время как

bar().resize(4711);

будет разрешено

Для встроенных модулей, таких как int, это не имеет никакого смысла вообще, потому что такие значения не могут быть изменены в любом случае.

(Я помню, как в Effective C++ обсуждали создание возвращаемого типа operator=() const Ссылка, правда, и это то, что нужно учитывать.)


Редактировать:

Похоже, что Скотт действительно дал этот совет. Если так, то по причинам, указанным выше, я нахожу это сомнительным даже для C++98 и C++ 03. Что касается C++11, я считаю это совершенно неправильным, как, кажется, сам Скотт обнаружил. Поправки к Effective C++, 3-е изд. он пишет (или цитирует других, кто жаловался):

Текст подразумевает, что все возвраты по значению должны быть константными, но случаи, когда неконстантные возвраты по значению являются хорошим дизайном, не трудно найти, например, возвращаемые типы std::vector, где вызывающие абоненты будут использовать swap с пустым вектором "захватить" содержимое возвращаемого значения, не копируя его.

И позже:

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

Вы можете пропустить суть совета Мейерса. Существенная разница заключается в const модификатор метода.

Это неконстантный метод (обратите внимание, нет const в конце), что означает, что ему разрешено изменять состояние класса.

int& operator[](const int index)

Это метод const (уведомление const в конце)

const int operator[](const int index) const

Что касается типов параметра и возвращаемого значения, то существует небольшая разница между int а также const int, но это не имеет отношения к сути совета. На что следует обратить внимание, чтобы возвращалась неконстантная перегрузка int& это означает, что вы можете назначить ему, например, num[i]=0, а перегрузка const возвращает неизменяемое значение (независимо от того, является ли возвращаемый тип int или же const int).

По моему личному мнению, если объект передается по стоимости, const модификатор лишний. Этот синтаксис короче и достигает того же

int& operator[](int index);
int operator[](int index) const;

Основная причина возврата значений в виде const заключается в том, что вы не можете сказать что-то вроде foo() = 5;, На самом деле это не проблема с примитивными типами, поскольку вы не можете назначить значения из примитивных типов, но это проблема с пользовательскими типами (например, (a + b) = c;с перегруженнымoperator+).

Я всегда находил оправдание этому довольно хрупким. Вы не можете остановить кого-то, кто намеревается написать неуклюжий код, и этот конкретный тип принуждения, по моему мнению, не имеет реальной выгоды.

В C++11 эта идиома на самом делеприносит много вреда: возврат значений в виде const предотвращает оптимизацию перемещения и, следовательно, его следует избегать, когда это возможно. По сути, я бы сейчас посчитал это анти-паттерном.

Вот тангенциально связанная статья, касающаяся C++11.

Когда эта книга была написана, совет был мало полезен, но, тем не менее, служил для предотвращения написания пользователем, например: foo() = 42; и ожидая, что это изменит что-то постоянное.

В случае operator[], это может немного сбить с толку, если вы также не предоставитеconst перегрузка, которая возвращает неconst ссылка, хотя вы, возможно, можете предотвратить эту путаницу, возвращая const ссылка или объект прокси вместо значения.

В наши дни это плохой совет, так как он мешает вам связать результат с (неconst) Значение ссылки.

(Как указано в комментариях, вопрос является спорным при возврате примитивного типа, как int, так как язык не позволяет назначать rvalue такого типа; Я говорю о более общем случае, который включает в себя возврат пользовательских типов.)

Для примитивных типов (например, int), постоянство результата не имеет значения. Для классов это может изменить поведение. Например, вы не сможете вызвать неконстантный метод для результата вашей функции:

class Bigint {
    const C foo() const { ... }
     ...
}

Bigint b;
b.foo().bar();

Выше запрещено bar() является постоянной функцией-членом C, В общем, выбирайте, что имеет смысл.

В примере с оператором индексации массива (operator[]) это имеет значение.

С

int& operator[](const int index) { /* ... */ }

Вы можете использовать индексирование для непосредственного изменения записи в массиве, например, используя это так:

mybigint[3] = 5;

Второй, const int operator[](const int) Оператор используется только для получения значения.

Тем не менее, в качестве возвращаемого значения из функций, для простых типов, таких как, например, int это не важно Когда это имеет значение, если вы возвращаете более сложные типы, скажем, std::vectorи не хочу, чтобы вызывающая функция изменяла вектор.

const тип возврата здесь не так важен. Поскольку временные переменные int не являются изменяемыми, нет заметной разницы в использовании int против const int, Вы бы увидели разницу, если бы использовали более сложный объект, который можно было бы изменить.

Есть несколько хороших ответов, касающихся технических особенностей двух версий. Для примитивного значения это не имеет значения.

Тем не менее, я всегда считал const быть там для программиста, а не для компилятора. Когда ты пишешь constвы явно говорите "это не должно измениться". Давайте смотреть правде в глаза, const обычно все равно можно обойти, верно?

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

РЕДАКТИРОВАТЬ: я тоже думаю, "использовать const по возможности "плохой совет. Вы должны использовать его там, где это имеет смысл.

Посмотри на это:

const int operator[](const int index) const

const в конце заявления. Здесь описывается, что этот метод можно вызывать по константам.

С другой стороны, когда вы пишете только

int& operator[](const int index)

он может быть вызван только для неконстантных экземпляров и также обеспечивает:

big_int[0] = 10;

синтаксис.

Одна из ваших перегрузок возвращает ссылку на элемент в массиве, который вы затем можете изменить.

int& operator[](const int index) { return _data[index]; }

Другая перегрузка возвращает значение для использования.

const int operator[](const int index) const { return _data[index]; }

Поскольку вы вызываете каждую из этих перегрузок одним и тем же способом, вы никогда не измените значение при его использовании.

int foo = myBigInt[1]; // Doesn't change values inside the object.
myBigInt[1] = 2; // Assigns a new value at index `
Другие вопросы по тегам