Деструктор аргумента функции вызывается по-разному в gcc и MSVC
При переносе некоторого кода на С++ из Microsoft Visual Studio в gcc я столкнулся с странной ошибкой, которая в конечном итоге сводилась к следующему:
#include <iostream>
using namespace std;
class Foo {
public:
int data;
Foo(int i) : data(i)
{
cout << "Foo constructed with " << i << endl;
}
Foo(const Foo& f) : data(f.data)
{
cout << "copy ctor " << endl;
}
Foo(const Foo&& f) : data(f.data)
{
cout << "move ctor" << endl;
}
~Foo()
{
cout << "Foo destructed with " << data << endl;
}
};
int Bar(Foo f)
{
cout << "f.data = " << f.data << endl;
return f.data * 2;
}
int main()
{
Foo f1(10);
Foo f2(Bar(std::move(f1)));
}
Если я компилирую и запускаю вышеуказанный код с помощью сообщества Microsoft Visual Studio 2015, я получаю следующий вывод:
Foo constructed with 10
move ctor
f.data = 10
Foo destructed with 10
Foo constructed with 20
Foo destructed with 20
Foo destructed with 10
Однако, если я компилирую и запускаю код с gcc 6.1.1 и -std = С++ 14, я получаю этот вывод:
Foo constructed with 10
move ctor
f.data = 10
Foo constructed with 20
Foo destructed with 10
Foo destructed with 20
Foo destructed with 10
gcc вызывает деструктор f
, аргумент Bar()
, после Bar()
возвращает, а msvc вызывает деструктор (по-видимому) до его возвращения или, по крайней мере, перед конструктором f2
. Когда f
предполагается разрушить, согласно стандарту С++?
Ответы
Ответ 1
Все в порядке; это зависит. Кажется, это указано в стандарте.
Из [expr.call]/4 (эта формулировка восходит к С++ 98);
Время жизни параметра заканчивается, когда возвращается функция, в которой она определена. Инициализация и уничтожение каждого параметра происходит в контексте вызывающей функции.
И CWG # 1880;
WG решила не указывать, уничтожены ли объекты параметров сразу после вызова или в конце полного выражения, к которому принадлежит вызов.
Как поведение g++ (и clang), так и MSVC было бы правильным, реализации могут выбирать один подход по сравнению с другим.
Все сказанное, если код, который у вас есть, зависит от этого заказа, я бы изменил его таким образом, чтобы упорядочение было более детерминированным - как вы видели, это приводит к тонким ошибкам.
Упрощенный пример такого поведения:
#include <iostream>
struct Arg {
Arg() {std::cout << 'c';}
~Arg() {std::cout << 'd';}
Arg(Arg const&) {std::cout << 'a';}
Arg(Arg&&) {std::cout << 'b';}
Arg& operator=(Arg const&) {std::cout << 'e'; return *this;}
Arg& operator=(Arg&&) {std::cout << 'f'; return *this;}
};
void func(Arg) {}
int main() {
(func(Arg{}), std::cout << 'X');
std::cout << std::endl;
}
Clang и g++ производят cXd
, а MSVC производит cdX
.