Кто копирует возвращаемое значение функции?

Является ли вызывающий или вызываемый объект копирующим или перемещающим возвращаемое значение функции? Например, если я хочу реализовать функцию pop () очереди, как это

template <typename T>
class queue
{
std::deque<T> d;
public:
// ... //
T pop()
{
// Creates a variable whose destructor removes the first
// element of the queue if no exception is thrown.
auto guard = ScopeSuccessGuard( [=]{ d.pop_front(); } );
return d.front();
}
}

вызывается деструктор моего охранника области после копирования переднего элемента?

РЕДАКТИРОВАТЬ: Дополнительный вопрос: будет ли линия

auto item = q.pop();

быть строго исключительным сейчас?

8

Решение

Возвращаемое значение копируется до выхода локальных переменных из области видимости. Копирование / перемещение может быть во временное местоположение (стек или регистр (ы)) или напрямую в собственный буфер вызывающего абонента или предпочтительные регистры — это проблема оптимизации / встраивания.

Когда используется временное расположение, компилятор должен организовать некоторое разделение работы между вызывающим и вызываемым объектами, и существует ряд соглашений для возвращаемых значений (и, конечно, параметров функций) для конкретных ОС и двоичных объектов / исполняемого формата, таких, что библиотеки / объекты, скомпилированные с одним компилятором, обычно могут использоваться с другим.

Будет ли линия …

auto item = q.pop();

…быть строго исключительным?

Если предположить, pop_front() не может throwинтересным случаем является возвращение временного местоположения, из которого значение снова копируется в буфер вызывающей стороны после возврата функции. Мне кажется, что вы не защитили себя от этого. Elision (вызываемый объект, непосредственно создающий возвращаемое значение в буфере / регистре (ах) результата вызывающего абонента) разрешен, но не обязателен.

Чтобы изучить это, я написал следующий код:

#include <iostream>

struct X
{
X() { std::cout << "X::X(this " << (void*)this << ")\n"; }
X(const X& rhs) { std::cout << "X::X(const X&, " << (void*)&rhs
<< ", this " << (void*)this << ")\n"; }
~X() { std::cout << "X::~X(this " << (void*)this << ")\n"; }

X& operator=(const X& rhs)
{ std::cout << "X::operator=(const X& " << (void*)&rhs
<< ", this " << (void*)this << ")\n"; return *this; }
};

struct Y
{
Y() { std::cout << "Y::Y(this " << (void*)this << ")\n"; }
~Y() { std::cout << "Y::~Y(this " << (void*)this << ")\n"; }
};

X f()
{
Y y;
std::cout << "f() creating an X...\n";
X x;
std::cout << "f() return x...\n";
return x;
};

int main()
{
std::cout << "creating X in main...\n";
X x;
std::cout << "x = f(); main...\n";
x = f();
}

Компилирование с g++ -fno-elide-constructors, мой вывод (с дополнительными комментариями) был:

creating X in main...
X::X(this 0x22cd50)
x = f(); main...
Y::Y(this 0x22cc90)
f() creating an X...
X::X(this 0x22cc80)
f() return x...
X::X(const X&, 0x22cc80, this 0x22cd40)   // copy-construct temporary
X::~X(this 0x22cc80)   // f-local x leaves scope
Y::~Y(this 0x22cc90)
X::operator=(const X& 0x22cd40, this 0x22cd50)  // from temporary to main's x
X::~X(this 0x22cd40)
X::~X(this 0x22cd50)

Очевидно, что назначение произошло после f() левая область действия: любое исключение из этого будет после того, как ваша защита области видимости (здесь обозначена Y) была уничтожена.

То же самое происходит, если основной содержит X x = f(); или же X x(f());за исключением того, что это конструктор копирования, который вызывается после уничтожения f()локальные переменные

(Я ценю, что поведение одного компилятора иногда является плохой основой для рассуждений о том, требуется ли что-либо для работы с помощью Стандарта, но с другой стороны, это значительно более надежно: когда он не работает, этот компилятор сломан — что относительно редко — или Стандарт не требует этого. Здесь поведение компилятора просто используется, чтобы добавить неподтвержденный вес моему впечатлению о требованиях Стандарта.)

Немного подробностей для любопытных: не то, чтобы обычно было полезно иметь код, который можно вызывать только одним способом, а то, что может быть быть в безопасности это const X& x = f();как const ссылка продлевает срок службы временного, но я не могу убедить себя, что стандарт требует иметь временную функцию, срок жизни которой был продлен, временную функцию, скопированную без дополнительной копии; для чего бы это ни стоило — это «сработало» в моей программе, и, что интересно, временное хранилище занимает то же место в стеке, которое используется при исключении возвращаемого значения, что предполагает f() код эффективно скомпилирован с возможностью элиды и -f-no-elide-constructors опция не столько отключает оптимизацию, сколько старается добавить пессимизацию: оставляя дополнительное место в стеке для временного объекта перед вызовом функции, затем добавляя дополнительный код для его копирования и уничтожая временный объект, затем перенастраивая указатель стека … ,

9

Другие решения

Копия возвращаемого значения выполняется вызываемой стороной и должна быть сделана до вызова деструкторов, так как в противном случае вы не могли бы вернуть значение / содержимое локальной переменной.

Вот соответствующий раздел в стандарте: раздел 12.4, пункт 11 (деструкторы)

Деструкторы вызываются неявно

  • для построенных объектов с автоматической продолжительностью хранения (3.7.3) при выходе из блока, в котором создан объект (6.7)

Я пытался найти место, где говорилось, что «возвращение происходит до разрушения», но в нем не говорится так ясно, как хотелось бы [если я что-то не упустил].

6