Продлевает ли член класса Const Reference временный срок?

171

Почему это:

#include <string>
#include <iostream>
using namespace std;

class Sandbox
{
public:
    Sandbox(const string& n) : member(n) {}
    const string& member;
};

int main()
{
    Sandbox sandbox(string("four"));
    cout << "The answer is: " << sandbox.member << endl;
    return 0;
}

Дайте вывод:

Ответ:

Вместо того:

Ответ: четыре

рукав моря
источник
39
И просто для удовольствия, если бы вы написали cout << "The answer is: " << Sandbox(string("four")).member << endl;, то это гарантированно сработает.
7
@RogerPate Не могли бы вы объяснить, почему?
Паоло М
16
Для кого-то, кому любопытно, пример, опубликованный Роджером Пейтом, работает, потому что строка («четыре») является временной и эта временная уничтожается в конце полного выражения , поэтому в его примере, когда SandBox::memberчитается, временная строка все еще жива .
PcAF
1
Вопрос заключается в следующем: поскольку написание таких классов опасно, существует ли предупреждение компилятора о передаче временных значений в такие классы , или существует руководство по проектированию (в Stroustroup?), Которое запрещает написание классов, в которых хранятся ссылки? Руководство по дизайну для хранения указателей вместо ссылок было бы лучше.
Grim Fandango
@PcAF: Не могли бы вы объяснить, почему временное string("four")уничтожается в конце полного выражения, а не после Sandboxвыхода из конструктора? В ответе Potatoswatter сказано, что временная привязка к элементу ссылки в ctor-initializer конструктора (§12.6.2 [class.base.init]) сохраняется до выхода из конструктора.
Тейлор Николс

Ответы:

166

Только местные const ссылки продлевают срок службы.

Стандарт определяет такое поведение в §8.5.3 / 5, [dcl.init.ref], разделе об инициализаторах ссылочных объявлений. Ссылка в вашем примере привязана к аргументу конструктора nи становится недействительной, когда объект nпривязан к выходу из области видимости.

Увеличение продолжительности жизни не транзитивно через аргумент функции. §12.2 / 5 [класс.время]:

Второй контекст, когда ссылка связана с временным. Временный объект, к которому привязана ссылка, или временный объект, являющийся полным объектом для подобъекта, к которому привязан временный объект, сохраняется в течение всего срока действия ссылки, за исключением случаев, указанных ниже. Временная привязка к элементу ссылки в ctor-initializer конструктора (§12.6.2 [class.base.init]) сохраняется до выхода из конструктора. Временная привязка к ссылочному параметру в вызове функции (§5.2.2 [expr.call]) сохраняется до завершения полного выражения, содержащего вызов.

Potatoswatter
источник
49
Вы также должны увидеть GotW # 88 для более понятного для человека объяснения: herbutter.com/2008/01/01/…
Натан Эрнст
1
Я думаю, что было бы более понятно, если бы в стандарте говорилось: «Второй контекст - это когда ссылка связана с prvalue». В коде OP можно сказать, что memberон привязан к временному объекту , поскольку инициализация memberс nпомощью средства для привязки memberк одному и тому же объекту nпривязана, и в данном случае это фактически временный объект.
ММ
2
@MM В некоторых случаях инициализаторы lvalue или xvalue, содержащие значение prvalue, будут расширять значение prvalue. В моём предложении P0066 рассматривается состояние дел.
Potatoswatter
1
Начиная с C ++ 11, ссылки на Rvalue также продлевают жизнь временного объекта, не требуя constкавычка.
GetFree
3
@KeNVinFavo да, используя мертвый объект всегда UB
Potatoswatter
30

Вот самый простой способ объяснить, что произошло:

В main () вы создали строку и передали ее в конструктор. Этот экземпляр строки существовал только внутри конструктора. Внутри конструктора вы назначили член, чтобы указывать непосредственно на этот экземпляр. Когда область видимости покинула конструктор, экземпляр строки был уничтожен, а член указал на строковый объект, которого больше не было. Если указатель Sandbox.member на ссылку за пределами его области действия, эти внешние экземпляры не будут удерживаться в области действия.

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

int main()
{
    string temp = string("four");    
    Sandbox sandbox(temp);
    cout << sandbox.member << endl;
    return 0;
}

Теперь temp будет выходить из области видимости в конце main (), а не в конце конструктора. Однако это плохая практика. Ваша переменная-член никогда не должна быть ссылкой на переменную, которая существует вне экземпляра. На практике вы никогда не знаете, когда эта переменная выйдет из области видимости.

Я рекомендую определить Sandbox.member как a. const string member;Это позволит скопировать данные временного параметра в переменную-член вместо назначения переменной-члена в качестве самого временного параметра.

Squirrelsama
источник
Если я сделаю это: const string & temp = string("four"); Sandbox sandbox(temp); cout << sandbox.member << endl;это все еще будет работать?
Ив
@Thomas const string &temp = string("four");дает тот же результат, что и const string temp("four"); , если вы не используете decltype(temp)специально
ММ
@MM Большое спасибо, теперь я полностью понимаю этот вопрос.
Ив
However, this is bad practice.- Зачем? Если и временный объект, и содержащий объект используют автоматическое хранение в одной и той же области, разве это не на 100% безопасно? И если вы этого не сделаете, что бы вы сделали, если строка слишком большая и слишком дорогая для копирования?
максимум
2
@max, потому что класс не заставляет передаваемый временный объект иметь правильную область видимости. Это означает, что однажды вы можете забыть об этом требовании, передать неверное временное значение, и компилятор не предупредит вас.
Алекс Че
5

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

  • cout << "The answer is: "Бит будет излучать "The answer is: "в буфер на стандартный вывод.

  • Затем << sandbox.memberбит будет содержать висячую ссылку operator << (ostream &, const std::string &), которая вызывает неопределенное поведение .

Из-за этого ничего не гарантируется. Программа может работать на первый взгляд нормально или может зависнуть даже без очистки стандартного вывода - это означает, что текст «Ответ:» не появится на вашем экране.

Tanz87
источник
2
Когда есть UB, поведение всей программы не определено - оно не только начинается в определенный момент выполнения. Поэтому мы не можем сказать наверняка, что "The answer is: "будет написано где угодно.
Тоби Спейт
0

Потому что ваша временная строка вышла из области видимости после того, как вернулся конструктор Sandbox, и стек, занятый ею, был освобожден для некоторых других целей.

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

Федор Сойкин
источник
7
«Никогда» - ужасно сильное слово.
Фред Ларсон
17
никогда не участвуйте в уроке, если вам не нужно хранить ссылку на объект. Есть случаи, когда вам нужно хранить ссылки на другие объекты, а не копии, для этих случаев ссылки являются более ясным решением, чем указатели.
Дэвид Родригес - dribeas
0

Вы имеете в виду что-то, что исчезло. Следующее будет работать

#include <string>
#include <iostream>

class Sandbox
{

public:
    const string member = " "; //default to whatever is the requirement
    Sandbox(const string& n) : member(n) {}//a copy is made

};

int main()
{
    Sandbox sandbox(string("four"));
    std::cout << "The answer is: " << sandbox.member << std::endl;
    return 0;
}
pcodex
источник