Согласно С++ 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, которая не использует стандартные термины секвенирования?