T const&t = C().a; продлить срок службы?

Дан следующий сценарий, который следует интерпретировать как код C++0x:

struct B { }; 
struct A { B b; }; 
int main() { 
  B const& b = A().b; 
  /* is the object still alive here? */
}

Clang и GCC (магистральная версия на 2011/02) ведут себя по-разному: Clang продлевает срок службы. GCC перемещает B в новый временный объект, а затем привязывает ссылку к этому новому временному объекту.

Я не могу найти какое-либо поведение, которое можно вывести из слов Стандарта. Выражение A().b не является временным (см. 5.2.5). Кто-нибудь может объяснить мне следующее?

  • Желаемое поведение (намерение комитета)
  • Поведение, полученное из FDIS

Спасибо!


person Johannes Schaub - litb    schedule 16.04.2011    source источник
comment
Как вы делаете вывод, что A().b не является временным из 5.2.5?   -  person Erik    schedule 17.04.2011
comment
@ Эрик, потому что так не сказано. Я не нашел ничего другого, чтобы сделать это. Как и следовало ожидать, 5.2.5 так и поступит (сравните с open-std.org/jtc1/sc22/wg21/docs/cwg_active.html#462), я упомянул 5.2.5, в котором определяется значение a.b.   -  person Johannes Schaub - litb    schedule 17.04.2011


Ответы (4)


В 12.2 пункт 5 N3126=10-0116 сказано, что:

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

а затем следует список из четырех особых случаев (ctor-инициализаторы, ссылочные параметры, возвращаемое значение, новый инициализатор).

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

РЕДАКТИРОВАТЬ

Думая о базовом подобъекте объекта, это также кажется единственным разумным поведением. Альтернатива будет означать выполнение нарезки:

Derived foo();
...
void bar()
{
    Base& x = foo(); // not very different from foo().b;
    ...
}

На самом деле после небольшого эксперимента кажется, что g++ действительно различает подобъект-член и базовый подобъект, но я не понимаю, где это различие делается в стандарте. Ниже приведена тестовая программа, которую я использовал, и где ясно видна различная обработка двух случаев... (B — базовый, D — производный, а C — составленный).

#include <iostream>

struct B
{
    B()
    { std::cout << "B{" << this << "}::B()\n"; }

    B(const B& x)
    { std::cout << "B{" << this << "}::B(const B& " << &x << ")\n"; }

    virtual ~B()
    { std::cout << "B{" << this << "}::~B()\n"; }

    virtual void doit() const
    { std::cout << "B{" << this << "}::doit()\n"; }
};

struct D : B
{
    D()
    { std::cout << "D{" << this << "}::D()\n"; }

    D(const D& x)
    { std::cout << "D{" << this << "}::D(const D& " << &x << ")\n"; }

    virtual ~D()
    { std::cout << "D{" << this << "}::~D()\n"; }

    virtual void doit() const
    { std::cout << "D{" << this << "}::doit()\n"; }
};

struct C
{
    B b;

    C()
    { std::cout << "C{" << this << "}::C()\n"; }

    C(const C& x)
    { std::cout << "C{" << this << "}::C(const C& " << &x << ")\n"; }

    ~C()
    { std::cout << "C{" << this << "}::~C()\n"; }
};

D foo()
{
    return D();
}

void bar()
{
    std::cout << "Before calling foo()\n";
    const B& b = foo();
    std::cout << "After calling foo()\n";
    b.doit();
    std::cout << "After calling b.doit()\n";

    const B& b2 = C().b;
    std::cout << "After binding to .b\n";
    b2.doit();
    std::cout << "After calling b2.doit()\n";
}

int main()
{
    std::cout << "Before calling bar()\n";
    bar();
    std::cout << "After calling bar()\n";
    return 0;
}

Вывод, который я получаю с g++ (Ubuntu/Linaro 4.4.4-14ubuntu5) 4.4.5,

Before calling bar()
Before calling foo()
B{0xbf9f86ec}::B()
D{0xbf9f86ec}::D()
After calling foo()
D{0xbf9f86ec}::doit()
After calling b.doit()
B{0xbf9f86e8}::B()
C{0xbf9f86e8}::C()
B{0xbf9f86e4}::B(const B& 0xbf9f86e8)
C{0xbf9f86e8}::~C()
B{0xbf9f86e8}::~B()
After binding to .b
B{0xbf9f86e4}::doit()
After calling b2.doit()
B{0xbf9f86e4}::~B()
D{0xbf9f86ec}::~D()
B{0xbf9f86ec}::~B()
After calling bar()

На мой взгляд, это либо ошибка в g++, либо ошибка в том, что требует стандарт C++, если это действительно ожидаемое поведение или возможное приемлемое поведение (но я должен сказать, что я действительно не думал об этом много, это просто ощущение, что что-то не так с этой дифференциацией).

person 6502    schedule 16.04.2011
comment
Это указывает на то, что действительно желаемый результат состоит в том, что срок службы увеличивается. Хорошая находка! Однако для этого требуется, чтобы A().b было временным выражением, чтобы мы в конечном итоге применяли этот абзац (когда ссылка привязана к временному выражению). Я не нашел спецификацию, говорящую об этом. - person Johannes Schaub - litb; 17.04.2011
comment
В документе также говорится or the temporary that is the complete object of a subobject to which the reference is bound. Вы привязываете ссылку к подобъекту b временного A(). - person 6502; 17.04.2011
comment
@ 6502 тот или временный, который есть ... относится к сохранению в течение всего времени существования ссылки, а не к тому моменту, когда ссылка привязана к временному. Так что весь текст пока при условии, что мы привязываем ссылку к временной (первое предложение абзаца), я думаю. - person Johannes Schaub - litb; 17.04.2011
comment
Вы хотите сказать, что подобъект временного объекта не является временным? В 3.7.5 сказано, что The storage duration of member subobjects, base class subobjects and array elements is that of their complete object... Я думаю, что нет настоящих сомнений в том, что подобъект временного объекта является временным. - person 6502; 17.04.2011
comment
Даже если подобъект временного объекта является временным объектом, это не означает, что выражение A().b является временным. Например: struct A { A &f() { return *this; } }; Мы можем позвонить A().f().f(). Время жизни временного объекта не заканчивается на операторе return, хотя время жизни временного объекта, связанного с возвращаемым значением в операторе возврата функции (6.6.3), не продлевается; временное уничтожается в конце полного выражения в операторе возврата. И я думаю, что причина в том, что *this не является временным выражением. - person Johannes Schaub - litb; 17.04.2011
comment
Temporary — это концепция времени компиляции, а не времени выполнения. Внутри тела функции void foo(const A& a){...} наверняка a не является временным, но ясно, что если я вызываю foo(A()), я передаю временное: объект является временным в контексте вызывающей стороны, и тот же самый объект времени выполнения не является временным в контексте вызываемой функции. A() является временным, A().b является подобъектом временного объекта, и вы привязываете ссылку к этому подобъекту, и это продлевает время жизни полного временного объекта, чтобы соответствовать сроку жизни ссылки. См. редактирование. - person 6502; 17.04.2011
comment
@ 6502, конечно, это не временно. Это не то, что говорит стандарт. Например, стандарт разрешает чтение из значения gl внутри константного выражения, если оно ссылается на временный объект, инициализированный из константного выражения (т. е. const int &a = 0;, тогда чтение из a является целочисленным константным выражением). Обратите внимание, что object здесь является временным. То, что вы описываете, требует, чтобы объект мог быть временным в какой-то момент, а позже, когда доступ по другому выражению больше не будет временным. Ничто в спецификации не описывает такое преобразование, афаикс. - person Johannes Schaub - litb; 17.04.2011
comment
@Johannes Schaub: я думаю, вы путаете уровень COMPILE-TIME и уровень RUN-TIME. Временные объекты - это концепция COMPILE-TIME... если у вас есть функция со ссылочным параметром, таким как void foo(A& a) { ... }, который является ссылкой, привязанной к невременному объекту... причина в том, что временные объекты возникают по очень конкретным причинам (например, вызов функции, преобразование типов...), а с a это не так. Код в функции никогда не уничтожит объект. Что является временным, а что нет, решается во ВРЕМЯ КОМПИЛИРОВАНИЯ, не зная, кто будет вызывать функцию. - person 6502; 17.04.2011
comment
@6502, а с a это не так. Правда, обращение к a не создает временное, поскольку временное уже было создано —> временное, на которое ссылается «a», возникает из-за привязки ссылки. В 12.2 говорится, что временные объекты типа класса создаются в различных контекстах: привязка ссылки к значению prvalue (8.5.3) ..., а в 8.5.3 говорится в последнем пункте. В противном случае создается и инициализируется временный объект типа 'cv1 T1'. ... - person Johannes Schaub - litb; 17.04.2011
comment
@Johannes Schaub: мне кажется, ты все еще упускаешь из виду мою мысль. В функции foo ссылка a и все. Компилятор должен сгенерировать код для foo, не зная, будет ли он вызываться с временным значением или нет. Временное является временным только для кода, который его создает... это свойство скомпилированного кода, а не объекта, созданного во время выполнения. С другой стороны, A() является временным объектом в вашем примере, а A().b, следовательно, является подобъектом временного объекта, и привязка ссылки к нему продлевает срок службы временного объекта. Это решение принимается во ВРЕМЯ КОМПИЛЯЦИИ. - person 6502; 17.04.2011
comment
Временный является временным только для кода, который его создает, я бы с вами согласился, но Стандарт с нами не согласился бы. Как я сказал выше, int const&a = 1; int x[a]; допустимо, потому что a относится к временному объекту, инициализированному константным выражением. Это демонстрирует: объект, на который делается ссылка, считается временным даже для кода, который не создавал или не нуждался во временном объекте. - person Johannes Schaub - litb; 17.04.2011
comment
Это просто еще один случай, когда время жизни временного объекта было увеличено, потому что объект привязан к ссылке. На самом деле это не отличается от A foo(){ return A(); } void bar(){ const A& a=foo(); a.do_something(); }. - person 6502; 18.04.2011

Хорошо, я делаю это на 180 градусов

Освежив свои знания о стандарте, я должен признать, что, вероятно, правильно ожидать, что объект, на который ссылается b, останется живым (будет расширен) на время действия области, в которой const& была инициализирована. . Я нашел GotW #88 полезный источник для этого.

Я не вижу, как A().b структурно или семантически отличается от

string f() { return "abc"; } // ABC initializes return-value **TEMP**

void g() {
const string& s = f();  // initializes with reference to a temp
  cout << s << endl;    // '*&s' is extended per standard
}

Извините за путаницу, которую я мог вызвать. Я был немного не в себе.

person sehe    schedule 16.04.2011
comment
f() является временным на 12.2. A().b не покрывается им (обратите внимание, что A().b напрямую связаны ссылками. 8.5.3 для них не создается временных). - person Johannes Schaub - litb; 17.04.2011

Временные объекты различаются по обстоятельствам их создания. (§12.2 Временные файлы типа класса создаются в различных контекстах…)

Для временных объектов, созданных ссылочным декларатором, §12.2 отсылает нас к §8.5. C++03 и C++11 сильно различаются в §8.5.3, но оба явно поддерживают ваш код.

С++ 03 говорит, что либо

— Ссылка привязана к объекту, представленному значением r (см. 3.10), или к подобъекту внутри этого объекта.

— Создается временный объект типа «cv1 T2» [так в оригинале], и вызывается конструктор для копирования всего объекта rvalue во временный объект. Ссылка привязана к временному объекту или к подобъекту во временном объекте.

Обсуждение полностью ведется с точки зрения подобъектов, не отличая базовые классы от членов. Таким образом, если привязка ссылки к члену запрещена, то привязка члена к базе запрещена, что исключает ScopeGuard.

C++11 более подробный, но указывает

— В противном случае ссылка должна быть ссылкой lvalue на неизменяемый константный тип (т. е. cv1 должна быть const) или ссылка должна быть ссылкой rvalue. … Если выражение инициализатора … является значением xvalue, классом prvalue, массивом prvalue или функцией lvalue, а «cv1 T1» совместимо по ссылке с «cv2 T2» … тогда ссылка привязывается к значению выражения инициализатора.

В сочетании с ответом 6502 и бессмысленностью привязки ссылки к значению, которое заканчивается точкой с запятой, очевидно, что С++ 11 продолжает поддерживать такое поведение.

person Potatoswatter    schedule 16.04.2011

Посмотрим (все ссылки на FDIS):

struct B { }; 
struct A { B b; }; 
int main() { 
  B const& b = A().b; 
}

1) 5.2.3/2 говорит, что A() является prvalue.

2) 5.2.5/4 говорит, что A().b является prvalue из-за пункта 1).

3) 8.5.3/5 говорит, что B const& b связывается напрямую с A().b без создания временного файла.

4) 12.2/5 говорит, что время жизни временной привязки к ссылке увеличено.

Так что, по крайней мере, кажется, что GCC ошибается здесь.

Верен ли Clang или это UB, зависит от того, является ли подобъект временного объекта временным. Я совершенно уверен, что ответ должен быть утвердительным, но Стандарт, похоже, ничего не говорит об этом. Должен ли кто-то представить DR?

EDIT: Как сказал @6502, 3.7.5 указывает, что время жизни подобъекта равно времени жизни его полного объекта.

person JohannesD    schedule 16.04.2011
comment
Я представил DR. Посмотрим, что они скажут. - person Johannes Schaub - litb; 17.04.2011