Bind const & amp; временного: нет предупреждения компилятора?

у меня есть 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, с дальнейшими испытаниями.

11

Решение

Без предупреждения, как без обид:

местный 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 для расширенной и более читаемой дискуссии на эту тему.

Неопределенное поведение

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

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

9

Другие решения

Связывание 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");
}

Конечно, вы не хотели бы здесь предупреждение.

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

2

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,

Все компиляторы не настолько умны, чтобы выяснить эти незначительные глюки …

1