константная ссылка на временную странность - PullRequest
7 голосов
/ 23 июня 2010

Мы все знаем, что подобные вещи действительны в c ++:

const T &x = T();

в то время как:

T &x = T();

нет.

В недавнем вопросе разговор привел к этому правилу. ОП опубликовал некоторый код, который явно напоминает UB. Но я бы ожидал, что модифицированная версия будет работать (это модифицированная версия):

#include <iostream>
using namespace std;

class A {
public:
    A(int k) { _k = k; };
    int get() const { return _k; };
    int _k;
};

class B {
public:
    B(const A& a) : _a(a) {}
    void b() { cout << _a.get(); }
    const A& _a;
};

B* f() {
    return new B(A(10));
}

int main() {
    f()->b();
}

Это печатает мусор на некоторых машинах, 10 на других ... звучит как UB для меня :-). Но потом я подумал: «1014» - это, по сути, прославленный «1015», все, что он делает, инициализирует и читает его. Почему бы просто не позвонить A int и посмотреть, что произойдет:

#include <iostream>
using namespace std;

typedef int A;

class B {
public:
    B(const A& a) : _a(a) {}
    void b() { cout << _a; }
    const A& _a;
};

B* f() {
    return new B(A(10));
}

int main() {
    f()->b();
}

Он печатает 10 каждый раз. По крайней мере, кажется , как правило ссылки на const, действующее для версии int, но не для версии класса. Они оба просто UB из-за использования кучи? Мне просто повезло с версией int, потому что компиляция просмотрела все const s и просто распечатала 10? Какой аспект правила мне не хватает?

Ответы [ 3 ]

15 голосов
/ 23 июня 2010

Это просто демонстрирует, что анализ поведения языка с помощью «пробного использования в компиляторе» обычно не дает никаких полезных результатов.Оба ваших примера недопустимы по одной и той же причине.

Срок действия временного элемента продлевается только в том случае, если вы используете этот временный объект в качестве прямого инициализатора для константной ссылки - только это создаст ссылку «времени жизни» междуссылка и временное.

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

Кроме того, в соответствии со стандартом C ++, если вы сделаете это

struct S {
  const int &r;

  S() : r(5) {
    cout << r; // OK
  }
};

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

S s;
cout << s.r; // Invalid

недействителен.

Ваш эксперимент с int просто «кажется, работает», чисто случайно.

3 голосов
/ 23 июня 2010

Он печатает 10 каждый раз.

Немного измените основную функцию, и она больше не будет печатать 10:

int main()
{
    B* p = f();
    cout << "C++\n";   // prints C++
    p->b();            // prints 4077568
}

как эта связь устанавливается на каком уровне?

См. 12.2 [класс.времен], §4 и §5:

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

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

Второй контекст - это когда ссылка связана с временным. Временный объект, к которому привязана ссылка, или временный объект, являющийся полным объектом подобъекта, к которому привязана ссылка, сохраняется в течение всего времени существования ссылки, кроме: [...]

Временная привязка к ссылочному параметру в вызове функции сохраняется до завершения полного выражения, содержащего вызов.

Так что в вашем случае временное уничтожается после вычисления полного выражения new B(A(10)).

3 голосов
/ 23 июня 2010

Тебе просто повезло. Изменение B :: b на это:

void b() {
    int i = rand();
    int j = rand();
    cout << _a << endl;
}

распечатывает случайные числа.

...