Что мне не хватает в стандарте С++ 11?

Я не возражаю против результата приведенного ниже кода, поскольку считаю правильным предположить, что ссылка const lvalue и ссылка rvalue продлевают время жизни временного объекта, возвращаемого функцией. Что меня удивляет, так это параграф в Стандарте, который, кажется, говорит об обратном:

12.2п5 (выделено мной):

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

  • ...
  • ...
  • Время жизни временной привязки к возвращаемому значению в операторе возврата функции (6.6.3) не продлевается; временное уничтожается в конце полного выражения в операторе возврата.

Пример кода:

#include <iostream>

struct A{ A() : i(2) {} int i;};

A f() { A a; return a; }

int main()
{
    A&& a1 = f();
    std::cout << a1.i << '\n';
    const A& a2 = f();
    std::cout << a2.i << '\n';
}

person Belloc    schedule 27.06.2013    source источник
comment
Я не понимаю, в чем ваш вопрос. Какой результат вы получаете от своей программы? Что в нем неожиданного? Какую версию какого компилятора вы тестируете?   -  person Jonathan Leffler    schedule 28.06.2013
comment
Я не думаю, что указанный вами пункт списка применим к вашему примеру. Я полагаю, что это относится к чему-то вроде return <temporary>;, а не к присвоению возвращаемого значения функции ссылке.   -  person David Brown    schedule 28.06.2013
comment
@JonathanLeffler Программа правильно печатает значения для a1.i и a2.i, показывая, что время жизни временного объекта, возвращаемого функцией, увеличивается.   -  person Belloc    schedule 28.06.2013
comment
@user1042389: user1042389: полагаться на такой вывод — плохая идея, поскольку память могла просто пережить атомную бомбу и по-прежнему содержать то, что в ней было раньше. Лучший тест выводит что-то непосредственно в конструкторах и деструкторе.   -  person Xeo    schedule 28.06.2013
comment
@DavidBrown Результат тот же, если функция f() была определена как A f() { return A(); }   -  person Belloc    schedule 28.06.2013
comment
@Xeo Если я включу еще один std::cout << a1.i << '\n'; в конце main(), он все равно выведет правильный результат.   -  person Belloc    schedule 28.06.2013
comment
@user1042389 user1042389 в этом случае возвращаемый тип не является ссылкой, поэтому временное значение присваивается возвращаемому значению, а затем уничтожается, продление срока службы не требуется. См. комментарий Xeo для примера, где применяется это исключение.   -  person David Brown    schedule 28.06.2013
comment
Не могу сказать ничего лучше, чем комментарий @Xeo. Это должен быть ответ.   -  person Kerrek SB    schedule 28.06.2013
comment
Я не знаю правил C++ в этой области, но обычно, если время жизни объекта заканчивается, это не гарантирует, что попытка доступа к нему будет неудачной. Возможно, часть памяти, в которой находился объект, не использовалась повторно для чего-то другого.   -  person Keith Thompson    schedule 28.06.2013
comment
@DavidBrown Тогда где в Стандарте находится пункт, в котором говорится, что ссылки во фрагменте продлевают время жизни временного объекта, возвращаемого функцией f ()?   -  person Belloc    schedule 28.06.2013
comment
@KeithThompson Нет сомнений в том, что срок службы временных материалов увеличен. Если вы определите деструктор для класса A с любым сообщением, оно будет напечатано в конце main().   -  person Belloc    schedule 28.06.2013
comment
В первой части вашей стандартной цитаты говорится, что время жизни временного объекта, созданного выражением f(), будет продлено, поскольку оно назначено ссылке. Исключение, которое вы цитируете, применяется только к функциям, которые возвращают ссылки.   -  person David Brown    schedule 28.06.2013


Ответы (2)


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

A f() { A a; return a; }

Прежде всего, a не является временным. Это может быть то, о чем вы думали:

A f() { return A(); }

Во-вторых, возвращаемый тип функции не является ссылочным. Вот когда будет применяться правило:

const A& f() { return A(); }

Временное из A() привязывается к возвращаемому типу const A&. По правилу срок службы временного не продлевается.

person Andrew Tomazos    schedule 27.06.2013
comment
Когда я читаю третий пункт списка в 12.2p5, я не вижу, как он связан со случаем A const& f(){ return A(); }, хотя я считаю, что это правильный ответ. Не могли бы вы объяснить это? - person Belloc; 28.06.2013
comment
@ user1042389: Таким образом, оператор return EXPR вызывает инициализацию возвращаемого значения функции выражением EXPR. Возвращаемое значение имеет возвращаемый тип функции. Я не уверен, что конкретно вы не понимаете. - person Andrew Tomazos; 28.06.2013

Упомянутая вами цитата предназначена специально для возврата ссылки из функции и привязки этой ссылки к временной:

const T& f() { return T(); };

В вашем коде это не так, потому что вы привязываете временную ссылку к ссылке на стороне вызова, а не в операторе возврата. В своих комментариях вы упоминаете, что при изменении кода на:

T f() { return T(); }
T&& r = f();

время жизни по-прежнему продлевается, но это неправильно. Временные жизни на время действия оператора return, в течение которого он копируется в возвращаемое значение. После завершения копирования время жизни временного заканчивается. На вызывающей стороне у вас есть другое временное (результат f()), время жизни которого увеличивается.

Но нет никаких сомнений в том, что срок службы временных конструкций продлевается. Если вы определите деструктор для класса A с любым сообщением, оно будет напечатано в конце main() или есть?

Это утверждение также неверно. Вы видите результаты оптимизации возвращаемого значения (RVO). Вместо создания временного объекта для T() внутри функции и другого для возвращаемого значения компилятор создает два объекта в одном и том же месте. Вы, вероятно, видите один объект в выводе программы, но теоретически их два.

Вы можете попробовать использовать g++ с -fno-elide-constructors, и вы сможете увидеть оба временных объекта, один из которых расширен, а другой не будет.

Кроме того, вы можете вернуть ссылку:

const A& f() { return A(); }
const A& r = f();

Что должно показать, как временное умирает до того, как r выйдет из области видимости.


По сути, это тот же тест, ингибирующий

$ g++ --версия | голова -1

g++ (ССЗ) 4.3.2

$ кошка x.cpp

#include <iostream>

struct X {
    X() { std::cout << "X\n"; }
    ~X() { std::cout << "~X\n"; }
};

X f() { return X(); }

int main() {
    const X& x = f();
    std::cout << "still in main()\n";
}

$ g++ -o t1 x.cpp && ./t1

X
still in main()
~X

$ g++ -fno-elide-constructors -o t2 x.cpp && ./t2

X
~X
still in main()
~X

$ clang++ -версия | голова -1

$ clang версии 3.2 (теги/RELEASE_32/final)

$ clang++ -fno-elide-constructors -o t3 x.cpp && ./t3

X
~X
still in main()
~X

$ кошка y.cpp

#include <iostream>

struct X {
    X() { std::cout << "X\n"; }
    ~X() { std::cout << "~X\n"; }
};

const X& f() { return X(); }

int main() {
    const X& x = f();
    std::cout << "still in main()\n";
}

$ g++ -fno-elide-constructors -o t4 y.cpp && ./t4

X
~X
still in main()
person David Rodríguez - dribeas    schedule 27.06.2013
comment
Временный умирает еще до инициализации r. Что бы вы ни получили из f(), это уже висящая отсылка. Кроме того, вы уверены насчет T& f()? Не хватает const или &? - person Xeo; 28.06.2013
comment
Во фрагменте нет шансов на RVO, поскольку функция возвращает именованный объект. - person Belloc; 28.06.2013
comment
@user1042389 Named Оптимизация возвращаемого значения (NRVO) хотела бы поговорить с вами. - person Xeo; 28.06.2013
comment
@user1042389: Из вашего комментария: Результат тот же, если функция f() была определена как A f() { return A(); }, сработает RVO. Даже в A f() { A a; return a; } компилятор выполнит Named RVOчто само по себе является формой РВО - person David Rodríguez - dribeas; 28.06.2013
comment
Надо сказать, что NRVO тоже не тот случай, так как вышеприведенные результаты были получены в отладочной сборке в VS2010. Компиляторы MS не применяют NRVO в отладочных сборках. - person Belloc; 28.06.2013
comment
@user1042389 user1042389: Я не знаю, что делает VS или когда он применяет разные вещи, которые называются (N) RVO. Я могу сказать вам, что в gcc вы можете форсировать создание всех объектов с помощью -fno-elide-constructors и что для T f() { return T(); } const T& r = f(); есть две трассировки запуска деструктора, одна для T() внутри функции, одна для временного объекта, время жизни которого продлевается на r. - person David Rodríguez - dribeas; 28.06.2013
comment
@ user1042389: Тестовый запуск аналогичной программы с g++ и clang++, запрещающими RVO и NRVO через -fno-elide-constructors. Из вывода программы должно быть очевидно, что время жизни не увеличено, если это не так, мы можем обсудить - person David Rodríguez - dribeas; 28.06.2013