Рассмотрим следующее:
struct A { /* ... */ };
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto a = foo();
Будет ли p.first
копироваться, перемещаться или редактироваться RVO?
Рассмотрим следующее:
struct A { /* ... */ };
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto a = foo();
Будет ли p.first
копироваться, перемещаться или редактироваться RVO?
Я обнаружил, что в Visual Studio 2010 и в gcc-5.1 RVO не применяется (см., например, http://coliru.stacked-crooked.com/a/17666dd9e532da76).
Соответствующий раздел стандарта — 12.8.31.1 [class.copy]. В нем говорится, что копирование разрешено (моя подсветка):
в операторе возврата в функции с типом возвращаемого значения класса, когда выражение является именем энергонезависимого автоматического объекта (кроме параметра функции или переменной, представленной объявлением исключения обработчик ([except.handle])) с тем же типом (игнорируя cv-квалификацию), что и тип возвращаемого значения функции, операцию копирования/перемещения можно опустить, встроив автоматический объект непосредственно в возвращаемое значение функции
Поскольку p.first
не является именем объекта, RVO запрещен.
auto& r = p.first
? Создает ли это новое имя в этом смысле?
- person Lightness Races in Orbit; 26.10.2015
r
— это имя ссылки, а не самого объекта. Так что, наверное, тоже не работает.
- person rozina; 26.10.2015
r
похоже соответствует этим критериям
- person M.M; 26.10.2015
p.first
относится к объекту (не к переменной, но относится к объекту), а first
— это имя (очевидно). И p.fist
относится к объекту автоматической продолжительности хранения. Но все же оказывается, что это не имя автоматического объекта. Такие глупости случаются в черновике C++. Вероятно, потому что p.first
— это не имя, что довольно тонко. Рассмотрим такую функцию, как T f() { return nonStaticDataMember; }
. Похоже, что это имя согласно пункту 3, но на самом деле это не так из-за неявного (*this).
, добавленного перед ним пунктом 9.
- person Johannes Schaub - litb; 27.10.2015
Просто чтобы добавить немного больше топлива, как бы это функционировало, если бы 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(); }
std::tie
, например.int n; A a; std::tie(a, n) = func(); return a;
- person Yankes   schedule 26.10.2015