Имеет ли "T const & t = C (). A;"удлинить время жизни "а"? - PullRequest
20 голосов
/ 17 апреля 2011

Дается следующий сценарий, который следует интерпретировать как код 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

Спасибо!

Ответы [ 4 ]

11 голосов
/ 17 апреля 2011

В п. 5 пункта 12.2 N3126 = 10-0116 говорится, что:

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

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

Итак (в этой версии) мне кажется, что 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 ++если это действительно ожидаемое поведение или возможное приемлемое поведение (но я должен сказать, что я не особо задумывался об этом, это просто ощущение, что что-то не так с этой дифференциацией).

1 голос
/ 17 апреля 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
}

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

0 голосов
/ 17 апреля 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?

РЕДАКТИРОВАТЬ: Как сказано в @ 6502, 3.7.5 указывает, что время жизни подобъекта - это время жизни его полного объекта.

0 голосов
/ 17 апреля 2011

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

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

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

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

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

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

C ++ 11 более многословен, но указывает

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

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

...