Подтвердить что ты не робот

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

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

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?

4b9b3361

Ответ 1

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

int const foo();

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

int& operator[]( int index );

и

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

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

Различие также имеет значение для возвращаемых значений типа класса: если вы return 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

Ответ 2

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

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

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

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

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

Параметры

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

Сама функция

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

Заключение

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

Ответ 3

В добавлении const к значениям без ссылки/не указателя и нет точки при добавлении его к встроенным.

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

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

то

foo().resize(42);

было бы запрещено, а

bar().resize(4711);

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

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

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


Edit:

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

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

И позже:

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

Ответ 4

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

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

int& operator[](const int index)

Этот метод является константным (уведомление 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;

Ответ 5

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

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

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

Здесь тангенциально связанная статья относительно С++ 11.

Ответ 6

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

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

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

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

Ответ 7

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

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

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

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

Ответ 8

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

const int operator[](const int index) const

the const в конце инструкции. Он описывает, что этот метод можно вызывать по константам.

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

int& operator[](const int index)

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

big_int[0] = 10;

синтаксис.

Ответ 9

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

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 `

Ответ 10

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

С

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

вы можете использовать индексирование для прямого изменения записи в массиве, например. используя его следующим образом:

mybigint[3] = 5;

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

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

Ответ 11

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

Ответ 12

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

Однако, я всегда считал const для программиста, а не для компилятора. Когда вы пишете const, вы явно говорите "это не должно меняться". Повернитесь лицом к лицу, const можно вообще обвести, верно?

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

EDIT: Я также думаю, что "используйте const, когда это возможно", это плохие советы. Вы должны использовать его там, где это имеет смысл.