Рассмотрим следующее:
struct A { /* ... */ };
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto a = foo();
Будет ли скопировано, перемещено или RVO-ed p.first
?
Рассмотрим следующее:
struct A { /* ... */ };
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto a = foo();
Будет ли скопировано, перемещено или RVO-ed p.first
?
Я нашел в Visual Studio 2010 и в gcc-5.1 RVO применяется не (см., например, http://coliru.stacked-crooked.com/a/17666dd9e532da76).
Соответствующий раздел стандарта - 12.8.31.1 [class.copy]. В нем указано, что разрешено копирование (мое выделение):
в операторе return в функции с типом возвращаемого класса, когда выражение представляет собой имя энергонезависимого автоматического объекта (кроме параметра функции или переменной, введенной исключающим - объявление обработчика ([except.handle])) с тем же типом (игнорируя cv-qualification) в качестве возвращаемого типа функции, операцию копирования/перемещения можно опустить, построив автоматический объект непосредственно в возвращаемое значение функции
Поскольку p.first
не является именем объекта, RVO запрещен.
Просто добавьте немного больше топлива, как бы эта функция функционировала, если RVO были в игре? Вызывающий помещает экземпляр A
где-то в памяти, а затем вызывает foo
, чтобы назначить ему (еще лучше, допустим, что этот A
был частью большей структуры и предположил, что он правильно выровнен так что следующий член структуры сразу после этого экземпляра A
). Предполагая, что RVO находятся в игре, часть first
p
расположена там, где ее вызывал абонент, но где находится int
, который находится second
? Он должен идти сразу после экземпляра A
для правильной работы pair
, но в исходном местоположении есть еще один член сразу после этого экземпляра A
.
Я бы ожидал, что RVO не будет происходить в этом месте, поскольку вы возвращаете только часть более крупного объекта. Переход может произойти, так как first
должен быть оставлен в разрушаемом состоянии.
@atkins попал сюда первым с ответом. Просто добавьте эту небольшую тестовую программу, которая может оказаться полезной в будущем при отслеживании поведения перемещения/назначения.
#include <iostream>
#include <string>
using namespace std::string_literals;
struct A {
A()
: history("created")
{
}
A(A&& r)
: history("move-constructed,"s + r.history)
{
r.history = "zombie: was "s + r.history;
}
A(const A& r)
: history("copied from: " + r.history)
{
}
~A() {
history = "destroyed,"s + history;
std::cout << history << std::endl;
}
A& operator=(A&& r) {
history = "move-assigned from " + r.history + " (was "s + history + ")"s;
r.history = "zombie: was "s + r.history;
return *this;
}
A& operator=(const A&r ) {
history = "copied from " + r.history;
return *this;
}
std::string history;
};
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto main() -> int
{
auto a = foo();
return 0;
}
Пример вывода:
destroyed,zombie: was created
destroyed,move-constructed,created
destroyed,copied from: move-constructed,created
Рассмотрим следующий код:
struct A {};
struct B {};
struct C { B c[100000]; };
A callee()
{
struct S
{
A a;
C c;
} s;
return s.a;
}
void caller()
{
A a = callee();
// here should lie free unused spacer of size B[100000]
B b;
}
"Частичный" RVO должен приводить к чрезмерному использованию стека в вызывающем абоненте, потому что (я думаю) S
может быть создан только полностью в кадре стека вызывающего абонента.
Другая проблема - поведение ~S()
:
// a.hpp
struct A {};
struct B {};
struct C { A a; B b; ~C(); };
// a.cpp
#include "a.hpp"
~C() { /* ... */; }
// main.cpp
#include "a.hpp"
A callee()
{
C c;
return c.a;
} // How to destruct c partially, having the user defined ~C() in another TU?
// Even if destructor is inline and its body is visible,
// how to automatically change its logic properly?
// It is impossible in general case.
void caller() { A a = callee(); }