Согласно C ++ 14 [expr.call] / 4:
Время жизни параметра заканчивается, когда возвращается функция, в которой он определен.
Кажется, это подразумевает, что деструктор параметра должен быть запущен до того, как код, вызвавший функцию, продолжит использовать возвращаемое значение функции.
Тем не менее, этот код показывает по-другому:
#include <iostream>
struct G
{
G(int): moved(0) { std::cout << "G(int)\n"; }
G(G&&): moved(1) { std::cout << "G(G&&)\n"; }
~G() { std::cout << (moved ? "~G(G&&)\n" : "~G()\n"); }
int moved;
};
struct F
{
F(int) { std::cout << "F(int)\n"; }
~F() { std::cout << "~F()\n"; }
};
int func(G gparm)
{
std::cout << "---- In func.\n";
return 0;
}int main()
{
F v { func(0) };
std::cout << "---- End of main.\n";
return 0;
}
Выход для gcc и clang, с -fno-elide-constructors
, (с моими аннотациями):
G(int) // Temporary used to copy-initialize gparm
G(G&&) // gparm
---- In func.
F(int) // v
~G(G&&) // gparm
~G() // Temporary used to copy-initialize gparm
---- End of main.
~F() // v
Итак, ясно v
конструктор работает раньше gparm
деструктор. Но в MSVC, gparm
уничтожен раньше v
конструктор работает.
Та же самая проблема может быть замечена с включенным разрешением копирования и / или с func({0})
так что параметр имеет прямую инициализацию. v
всегда строится раньше gparm
разрушен. Я также наблюдал проблему в более длинной цепочке, например F v = f(g(h(i(j())));
не уничтожил ни один из параметров f,g,h,i
до после v
был инициализирован.
Это может быть проблемой на практике, например, если ~G
разблокирует ресурс и F()
приобретает ресурс, это будет тупик. Или если ~G
бросает, тогда выполнение должно перейти к обработчику catch без v
будучи инициализированным.
У меня вопрос: разрешает ли стандарт оба этих заказа? , Есть ли более конкретное определение отношения секвенирования, связанное с уничтожением параметров, чем просто та цитата из expr.call/4, в которой не используются стандартные термины секвенирования?
На самом деле я могу ответить на свой собственный вопрос … не нашел ответа во время поиска, прежде чем писать его, но потом снова поискал потом нашел ответ (типично да).
Во всяком случае: эта проблема CWG # 1880 с разрешением:
Примечания от встречи в июне 2014 года:
WG решила не указывать, уничтожаются ли объекты параметров сразу после вызова или в конце полного выражения, которому принадлежит вызов.
Последний мой набросок C ++ 17 (N4606) изменил текст в [expr.call] / 4:
Это зависит от реализации, заканчивается ли время жизни параметра, когда возвращается функция, в которой он определен, или в конце включающего полного выражения.
Я полагаю, что мы должны рассматривать эту резолюцию (то есть «определяемую реализацией») как применяемую задним числом, поскольку она не была четко определена опубликованными стандартами.
Примечание: определение полное выражение можно найти в C ++ 14 [intro.execution] / 10:
Полное выражение — это выражение, которое не является подвыражением другого выражения. […] Если языковая конструкция определена для создания неявного вызова функции, использование языковой конструкции считается выражением для целей этого определения.
Так F v { func(0) };
это вложение полное выражение за gparm
(хотя это декларация, а не выражение!).
Других решений пока нет …