c++ - ¿Funciona RVO en los miembros del objeto?
c++11 visual-c++ (4)
@atkins llegó primero con la respuesta. Simplemente agregue este pequeño programa de prueba que puede serle útil en el futuro cuando rastree el comportamiento de mover / asignar.
#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;
}
ejemplo de salida:
destroyed,zombie: was created
destroyed,move-constructed,created
destroyed,copied from: move-constructed,created
Considera lo siguiente:
struct A { /* ... */ };
A foo() {
auto p = std::make_pair(A{}, 2);
// ... do something
return p.first;
}
auto a = foo();
¿ p.first
será copiado, movido o RVO-ed?
Lo he encontrado en Visual Studio 2010 y en gcc-5.1 no se aplica RVO (consulte, por ejemplo, http://coliru.stacked-crooked.com/a/17666dd9e532da76 ).
La sección relevante del estándar es 12.8.31.1 [class.copy]. Establece que la elisión de copia está permitida (mi destaque):
en una instrucción de retorno en una función con un tipo de retorno de clase, cuando la expresión es el nombre de un objeto automático no volátil (que no sea un parámetro de función o una variable introducida por la declaración de excepción de un controlador ([except.handle]) )) con el mismo tipo (ignorando cv-qualification) como el tipo de retorno de función, la operación copy / move puede omitirse construyendo el objeto automático directamente en el valor de retorno de la función
Como p.first
no es el nombre de un objeto, RVO está prohibido.
Solo para agregar un poco más de combustible, ¿cómo funcionaría esto si RVO estuviera en juego? La persona que llama ha puesto una instancia de A
en algún lugar de la memoria y luego llama a foo
para asignarla (incluso mejor, supongamos que esa A
era una parte de una estructura más grande, y supongamos que está alineada correctamente de modo que el siguiente miembro de la estructura es inmediatamente después de esa instancia de A
). Suponiendo que RVO estuviera en juego, la first
parte de p
se encuentra donde la llamaba, pero ¿dónde se ubica el int
que está en second
lugar? Tiene que ir justo después de la instancia de A
para mantener el pair
funcionando correctamente, pero en la ubicación de origen, hay otro miembro justo después de esa instancia de A
Esperaría que RVO no estuviera sucediendo en este lugar, ya que solo estás devolviendo una porción de un objeto más grande. Un movimiento podría ocurrir ya que first
tendría que dejarse en un estado destructible.
Considera seguir el código:
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;
}
El RVO "parcial" debería ocasionar una hinchazón excesiva del uso de la pila en la persona que llama, porque (creo) que S
se puede construir solo por completo en el marco de la pila de llamadas.
Otro problema es el comportamiento ~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(); }