у меня есть 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, с дальнейшими испытаниями.
Без предупреждения, как без обид:
местный const
ссылки продлевают срок службы переменной.
Стандарт определяет такое поведение в §8.5.3 / 5, [dcl.init.ref], разделе об инициализаторах ссылочных объявлений. Увеличение продолжительности жизни не транзитивно через аргумент функции. §12.2 / 5 [класс.время]:
Второй контекст, когда ссылка связана с временным. Временное, к которому привязана ссылка, или временное, которое является
полный объект к подобъекту, с которым связан временный объект
сохраняется в течение всего срока действия ссылки, кроме случаев, указанных ниже.
Временная привязка к ссылочному элементу в конструкторе
ctor-initializer (§12.6.2 [class.base.init]) сохраняется до
конструктор выходит. Временная граница с опорным параметром в
вызов функции (§5.2.2 [expr.call]) сохраняется до завершения
полное выражение, содержащее вызов.
Вы можете взглянуть на gotw-88 для расширенной и более читаемой дискуссии на эту тему.
Так это твой код правильный? Нет, и его выполнение приведет к неопределенному поведению. Настоящая проблема в вашем снимке кода заключается в том, что неопределенное поведение вызвано сочетанием двух совершенно правовой операции: вызов конструктора, передающий временный объект (чья жизнь продолжается внутри блока конструктора) и привязка ссылки в определении конструктора.
Компилятор не достаточно умен чтобы обнаружить эту взрывную комбинацию операторов, поэтому вы не получите никакого предупреждения.
Связывание const &
для временного является допустимым, и компилятор будет гарантировать, что временный будет жить по крайней мере столько же, сколько ссылка. Это позволяет вам делать такие вещи, как передавать строковые литералы в функции, ожидающие const std::string &
,
Однако в вашем случае вы копируете эту ссылку, и, следовательно, пожизненная гарантия больше не действует. Ваш конструктор завершает работу, временный объект уничтожается, и у вас остается ссылка на недопустимую память.
Проблема в том, что нет единой точки, в которой было бы оправданным предупреждение. Только комбинация вызова конструктора и его реализации приводит к неопределенному поведению.
Если вы рассматриваете только конструктор:
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");
}
Конечно, вы не хотели бы здесь предупреждение.
Подводя итог, можно сказать, что как сайт вызова, так и реализация конструктора прекрасно используются как есть. Только их комбинация вызывает проблемы, но эта комбинация выходит за рамки контекста, который разумно может использовать компилятор для выдачи предупреждений.
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's
время жизни истекло d's
,
Все компиляторы не настолько умны, чтобы выяснить эти незначительные глюки …