Чи слід повертати об'єкти const?


78

У 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?


4
Гарне запитання - хоча в цьому випадку чи не метод зазвичай повертає посилання в обох випадках?
Ken Wayne VanderLinde,

Гарне питання. Я пропоную вам змінити його на int operator[](int i)проти const int operator[](const int i).
Олександр Чертов

1
@KenWayneVanderLinde, ні. Викликаний метод залежить від того, чи є у нас Bigint&чи a const Bigint&.
Олександр Чертов

1
@AlexanderChertov ні: він запитує про тип повернення, а не про тип аргументу, тому питання має зосереджуватися лише на цьому
stijn

Чому? Мені особисто цікаво, чому б хтось мав const int iаргументацію?
Олександр Чертов

Відповіді:


88

CV-кваліфікатори верхнього рівня для типів повернення некласного типу ігноруються. Це означає, що навіть якщо ви пишете:

int const foo();

Повертається тип int. Якщо тип повернення є посиланням, звичайно, constце вже не верхній рівень, а різниця між:

int& operator[]( int index );

і

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

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

Різниця також актуальна для повернутих значень класу типу: якщо ви повернетесь T const, то абонент не може викликати функції, що не є 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

6
ff та gg оголошені як функції, що повертають Test. Якщо так, то виклики мають бути ff (). F () тощо ... Якщо ff є екземпляром класу Test, дужки слід видалити з оголошення (+1 однак :)
user396672

@ user396672 (і BJ ...) Так. Я забув ()після ffі gg. Я їх додам. Дякую за виправлення.
Джеймс Канце,

+1 для всього після "Різниця також актуальна для повернутих значень класу". Ця програма constкорисна, проте багато людей, здається, або не помічають її, або наполягають на тому, що повернення constвартості не має ніякої корисності.
underscore_d

4
-1 «Невірно вказано« Кваліфікатори CV верхнього рівня на типи повернення некласного типу ». Вони все ще є частиною типу функції. Однак cv-кваліфікація першого значення некласового типу ігнорується, так що виклик функції, що повертається int const, скажімо, еквівалентний виклику цієї функції, модифікованої для повернення int. Standardese у C ++ 11 §3.10 / 4, “Значення класу можуть мати типи, що відповідають стандарту cv; некласові значення завжди мають некваліфіковані типи cv ”.
Вітаю і hth. - Альф

38

Ви повинні чітко розмежовувати використання const, що застосовується до повернутих значень, параметрів та самої функції.

Повернути значення

  • Якщо функція повертається за значенням , const не має значення, оскільки копія об'єкта. Однак це матиме значення в C ++ 11 із залученою семантикою переміщення.
  • Це також ніколи не має значення для базових типів, оскільки вони завжди копіюються.

Поміркуйте const std::string SomeMethod() const. Це не дозволить використовувати (std::string&&)функцію, оскільки вона очікує неконстантного значення. Іншими словами, повернутий рядок завжди буде скопійовано.

  • Якщо функція повертається за посиланням , constзахищає повернутий об'єкт від модифікації.

Параметри

  • Якщо ви передаєте параметр за значенням , const запобігає зміні заданого значення функцією у функції . Вихідні дані з параметра все одно не можна змінити, оскільки у вас є лише копія.
  • Зверніть увагу, що оскільки копія завжди створюється, const має значення лише для тіла функції , отже, це перевіряється лише у визначенні функції, а не в декларації (інтерфейсі).
  • Якщо ви передаєте параметр за посиланням , застосовується те саме правило, що і у повернутих значеннях

Сама функція

  • Якщо функція є constв кінці, вона може запускати лише інші constфункції, і не може змінювати або дозволяти модифікацію даних класу. Таким чином, якщо воно повертається за посиланням, повернене посилання має бути const. Тільки функції const можна викликати на об'єкті або посилання на об'єкт, який є самим const. Також змінні поля можна змінювати.
  • Поведінка, створена компілятором, змінює thisпосилання на T const*. Функція може завждиconst_cast this , але, звичайно, цього робити не слід, і це вважається небезпечним.
  • Звичайно, розумно використовувати цей специфікатор лише для методів класу; глобальні функції з const в кінці викличуть помилку компіляції.

Висновок

Якщо ваш метод не змінює і ніколи не змінить змінні класу, позначте його як const і переконайтесь, що він відповідає усім необхідним критеріям. Це дозволить писати більш чистий код, тим самим зберігаючи його правильним . Однак покласти constскрізь, не замислюючись, звичайно, не шлях.


4
Дві виправлення: по-перше, для повернення за значенням верхній рівень constігнорується для некласових типів . Це має значення для типів класів. А для параметрів, переданих за значенням, верхній рівень constігнорується в оголошеннях функцій. Це має значення лише у визначенні.
Джеймс Канце,

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

Що стосується constфункції: вона змінює тип this, який є T const*, а не T*. Усі інші ефекти випливають із цього. (І це не заважає будь-яким модифікаціям об'єкта: mutableдані все ще можна змінювати, а функція може законно відкидати const і змінювати все, що побажає.)
Джеймс Канце,

"Якщо функція повертається за посиланням, вона захищає повернутий об'єкт від модифікації" false
NoSenseEtAl

Ой, я мав на увазі ", [const] захищає об'єкт" ... Я виправлю це, щоб було зручніше читати.
Бартек Баначевич,

30

Існує мало користі в додаванні constкваліфікації до неопорної / НЕ покажчик rvalues, і немає сенсу в додаванні його вбудованих модулів.

У разі певного користувача типів , A constкваліфікація буде перешкоджати викликають абонентам від виклику до не- constфункції - члена на повернутому об'єкті. Наприклад, дано

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

тоді

foo().resize(42);

було б заборонено, поки

bar().resize(4711);

буде дозволено.

Для вбудованих подібних int це взагалі не має сенсу, оскільки такі значення r не можуть бути змінені.

(Я пам'ятаю , Ефективне використання C ++ обговорює зробити тип повернення operator=()в constзасланні, хоча, і це що - то розглянути.)


Редагувати:

Здається, Скотт справді дав таку пораду . Якщо так, то з причин, наведених вище, я вважаю це сумнівним навіть для C ++ 98 та C ++ 03 . Що стосується С ++ 11, я вважаю це абсолютно неправильним , як, здається, виявив сам Скотт. В помилці для ефективного C ++, 3-е видання. , він пише (або цитує інших, хто скаржився):

З тексту випливає, що всі повернення за значеннями повинні бути const, але випадків, коли повернення за значенням non-const є гарним дизайном, важко знайти, наприклад, типи повернення std :: vector, де абоненти будуть використовувати swap з порожнім вектором "захопити" вміст поверненого значення без їх копіювання.

А пізніше:

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


1
Мені подобається згадування про помилки за порадою Скотта. Дійсно допомагає пролити світло на те, що в іншому випадку могло б заплутати людей, які
вперше вступили

2
@sehe: Я надіслав Скотту лист із запитанням щодо цієї проблеми, і він відповів, що ці коментарі про помилки відображають його поточне мислення з цього питання.
sbi

17

Ви можете пропустити сенс поради Мейєрса. Суттєва різниця полягає вconst модифікаторі методу.

Цей метод не є 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;

5
Власне, питання полягає у поверненні constзначення.
juanchopanza

@juanchopanza: Я сумніваюся, OP мав на увазі саме це. Порівняйте його fooприклад із operator[]прикладом. Він може пропустити думку, що constмодифікатор методу робить різницю, а не тип повернення.
Андрій

13

Основною причиною повернення значень як const є те, що ви не можете сказати щось подібне foo() = 5;. Це на самому ділі не проблема з примітивними типами, так як ви не можете призначити rvalues примітивних типів, але це проблема з користувачем типів (як (a + b) = c;, з перевантаженим operator+).

Я завжди знаходив виправдання для цього досить хитрого. Ви не можете зупинити когось, хто має намір писати незграбний код, і цей конкретний тип примусу, на мій погляд, не має реальної вигоди.

З C ++ 11 насправді є багато шкоди, яку завдає ця ідіома: повернення значень як const перешкоджає оптимізації переміщення, і тому слід уникати, коли це можливо. В основному, я б зараз вважав це анти-шаблоном.

Ось дотична стаття, що стосується C ++ 11.


що сказати про те, що модифікувати значення, що повертається, має сенс? Як MyClass.isInitialized (). Якщо абонент змінює повернене значення, я перейду до WTF при перегляді коду.
NoSenseEtAl

@NoSenseEtAl: Хто я такий, щоб знати, що безглуздо?
Наскільки

Я додав приклад. До мого минулого коментаря. А ви дизайнер класу, тому розумієте operator = ваших типів повернення: D. Тож ви знаєте, чи безглуздо для абонента змінювати результат ваших методів.
NoSenseEtAl

9

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

У випадку з operator[], це може дещо заплутати, якщо ви також не надаєте constперевантаження, яке повертає не- constпосилання, хоча, можливо, ви можете запобігти цій плутанині, повернувши constпосилання або об'єкт-проксі замість значення.

У наші дні це погана порада, оскільки вона заважає вам прив’язати результат до (не const) посилання на rvalue.

(Як вказувалося в коментарях, питання спірне при поверненні примітивного типу типу int, оскільки мова заважає вам привласнювати rvalueтип такого типу; я говорю про більш загальний випадок, який включає повернення визначених користувачем типів. )


10
Його приклад повернувся int. foo() = 42;є незаконним, незалежно від того, оголошено тип повернення intчи int const. Я не думаю, що Скотт сперечався за використання таких constмісць, як типи повернення та параметри в оголошеннях функцій, де компілятор ігнорує їх.
James Kanze,

@JamesKanze: Дякую, що вказали на це; Я повинен був дати зрозуміти, що я говорив загальніше.
Mike Seymour

2

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

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

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

Вищезазначене заборонено, якщо bar()є функцією-членом const C. Загалом, вибирайте, що має сенс.


0

Подивіться на це:

const int operator[](const int index) const

в constкінці заяви. Він описує, що цей метод можна викликати на константах.

З іншого боку, коли ви пишете лише

int& operator[](const int index)

його можна викликати лише у неконстантних екземплярах, а також забезпечити:

big_int[0] = 10;

синтаксис.


0

Одне з ваших перевантажень - повернення посилання на елемент у масиві, який ви потім зможете змінити.

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 `

0

У прикладі оператора індексації масиву ( operator[]) це має значення.

С

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

Ви можете використовувати індексацію, щоб безпосередньо змінити запис у масиві, наприклад, використовуючи його так:

mybigint[3] = 5;

Другий, const int operator[](const int)оператор використовується для отримання лише значення.

Однак, як повернене значення з функцій, для простих типів, таких як, наприклад, intце не має значення. Важливо, якщо ви повертаєте більш складні типи, скажіть a std::vector, і не хочете, щоб виклик функції модифікував вектор.


0

Тип constповернення тут не так важливий. Оскільки ІНТ не тимчасові конструкції змінювані, немає спостережуваних відмінностей у використанні intпроти const int. Ви бачили б різницю, якби використовували більш складний об’єкт, який можна було б змінити.


0

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

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

Коли ви повертаєте a const , ви говорите програмісту, який використовує цю функцію, що значення не повинно змінюватися. І якщо він його змінює, він, мабуть, робить щось не так, бо йому / їй не потрібно було б це робити.

РЕДАГУВАТИ: Я також вважаю "використовувати, constколи це можливо" - погана порада. Ви повинні використовувати його там, де це має сенс.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.