Bind const & of временного: нет предупреждения компилятора?
У меня есть TestClass
с переменной-членом const&
. Я знаю из разных мест и из собственного опыта, что это плохая идея инициализировать этот const&
со ссылкой на временное значение. Поэтому я был очень удивлен, что следующий код будет хорошо скомпилирован (протестирован с gcc-4.9.1
, clang-3.5
и scan-build-3.5
), но не сможет работать должным образом.
class TestClass {
public:
// removing the "reference" would remove the temporary-problem
const std::string &d;
TestClass(const std::string &d)
: d(d) {
// "d" is a const-ref, cannot be changed at all... if it is assigned some
// temporary value it is mangled up...
}
};
int main() {
// NOTE: the variable "d" is a
// temporary, whose reference is not valid... what I don't get in the
// moment: why does no compiler warn me?
TestClass dut("d");
// and printing what we got:
std::cout << "beginning output:\n\n";
// this will silently abort the program (gcc-4.9.1) or be empty
// (clang-3.5) -- don't know whats going on here...
std::cout << "dut.d: '" << dut.d << "'\n";
std::cout << "\nthats it!\n";
return 0;
}
Почему ни один из двух компиляторов не предупреждает меня во время компиляции? См. также этот ideone, и еще несколько тестирований.
Ответы
Ответ 1
Без предупреждения, так как без обид:
локальные ссылки const
продлевают срок службы переменной.
Стандарт определяет такое поведение в §8.5.3/5, [dcl.init.ref], разделе об инициализаторах ссылочных объявлений. Увеличение продолжительности жизни не транзитивно через аргумент функции. §12.2/5 [class.tevent]:
Второй контекст, когда ссылка связана с временным. Временное, к которому привязана ссылка, или временное, которое является полный объект к подобъекту, с которым связан временный объект сохраняется в течение всего срока действия ссылки, кроме случаев, указанных ниже. Временная привязка к ссылочному элементу в конструкторах ctor-initializer (§12.6.2 [class.base.init]) сохраняется до конструктор выходит. Временная граница с опорным параметром в вызов функции (§5.2.2 [expr.call]) сохраняется до завершения полное выражение, содержащее вызов.
Вы можете взглянуть на gotw-88 для расширенного и более удобного обсуждения этой темы.
Неопределенное поведение
Так это твой код правильный? Нет, и его выполнение приведет к неопределенному поведению. Настоящая проблема в снимке кода заключается в том, что неопределенное поведение вызвано сочетанием двух совершенно допустимых операций: вызова конструктора, передающего временный объект (срок жизни которого находится внутри блока конструктора) и привязку ссылки в определении конструктора.
Компилятор не достаточно умен, чтобы обнаружить эту взрывную комбинацию операторов, поэтому вы не получаете никаких предупреждений.
Ответ 2
Привязка const &
к временному действительна, и компилятор будет гарантировать, что временный объект будет жить по крайней мере столько же, сколько и ссылка. Это позволяет вам делать такие вещи, как передавать строковые литералы в функции, ожидающие const std::string &
.
Однако в вашем случае вы копируете эту ссылку, и, следовательно, пожизненная гарантия больше не действует. Ваш конструктор завершается, временный объект уничтожается, и вы получаете ссылку на недопустимую память.
Ответ 3
Проблема заключается в том, что нет единой точки, в которой было бы оправданным предупреждение. Это только сочетание вызова конструктора и его реализации, что приводит к неопределенному поведению.
Если вы рассматриваете только конструктор:
class TestClass {
public:
const std::string &d;
TestClass(const std::string &d)
: d(d)
{}
};
Здесь нет ничего плохого, у вас есть ссылка, и вы ее храните. Вот пример совершенно правильного использования:
class Widget {
std::string data;
TestClass test;
public:
Widget() : data("widget"), test(data)
{}
};
Если вы рассматриваете только сайт вызова:
//Declaration visible is:
TestClass(const std::string &d);
int main() {
TestClass dut("d");
}
Здесь компилятор не "видит" (в общем случае) определение конструктора. Представьте себе альтернативу:
struct Gadget {
std::string d;
Gadget(cosnt std::string &d) : d(d) {}
};
int main()
{
Gadget g("d");
}
Конечно, вы не хотели бы здесь предупреждение.
Подводя итог, можно сказать, что как сайт вызова, так и реализация конструктора прекрасно используются как есть. Только их комбинация вызывает проблемы, но эта комбинация выходит за рамки контекста, который разумно может использовать компилятор для выдачи предупреждений.
Ответ 4
TestClass(const std::string &d1)
: d(d1) {
TestClass dut("d");
Я думаю, что логически происходит следующее: -
1) Ваш строковый литерал ("d")
будет неявно преобразован в std::string (давайте дадим ему имя 'x'
).
2) Итак, 'x'
является временным, который связан с d1
здесь. Срок действия этого временного промежутка продлевается до срока службы вашего d1
. Хотя этот строковый литерал всегда будет жив до конца программы.
3) Теперь вы делаете 'd' refer to 'd1'
.
4) В конце вашего конструктора d1
время жизни истекло, как и d
.
Все компиляторы не настолько умны, чтобы выяснить эти незначительные глюки...