c++ - sirve - Sobrecarga en la referencia, frente al único paso por valor+std:: ¿mover?
paso de datos por referencia en c (1)
Parece que el principal consejo relacionado con los valores de C ++ 0x es agregar movimientos de constructores y mover operadores a sus clases, hasta que los compiladores los implementen por defecto.
Pero esperar es una estrategia perdedora si usa VC10, porque la generación automática probablemente no estará aquí hasta VC10 SP1, o en el peor de los casos, VC11. Probablemente, la espera para esto se medirá en años.
Aquí está mi problema. Escribir todo este código duplicado no es divertido. Y es desagradable mirar. Pero esta es una carga bien recibida, para aquellas clases consideradas lentas. No es así para los cientos, si no miles, de clases más pequeñas.
:: Suspiros :: Se suponía que C ++ 0x me permitía escribir menos código, ¡no más!
Y entonces tuve un pensamiento. Compartido por muchos, supongo.
¿Por qué no simplemente pasar todo por valor? ¿Std :: move + copy elision hará que esto sea casi óptimo?
Ejemplo 1 - Constructor típico Pre-0x
OurClass::OurClass(const SomeClass& obj) : obj(obj) {}
SomeClass o;
OurClass(o); // single copy
OurClass(std::move(o)); // single copy
OurClass(SomeClass()); // single copy
Contras: Una copia desperdiciada por valores.
Ejemplo 2 - ¿Recomendado C ++ 0x?
OurClass::OurClass(const SomeClass& obj) : obj(obj) {}
OurClass::OurClass(SomeClass&& obj) : obj(std::move(obj)) {}
SomeClass o;
OurClass(o); // single copy
OurClass(std::move(o)); // zero copies, one move
OurClass(SomeClass()); // zero copies, one move
Pros: Presumiblemente el más rápido.
Contras: Un montón de código!
Ejemplo 3 - Pasar por valor + std :: mover
OurClass::OurClass(SomeClass obj) : obj(std::move(obj)) {}
SomeClass o;
OurClass(o); // single copy, one move
OurClass(std::move(o)); // zero copies, two moves
OurClass(SomeClass()); // zero copies, one move
Pros: No hay código adicional.
Contras: Un movimiento perdido en los casos 1 y 2. El rendimiento sufrirá mucho si SomeClass
no tiene un constructor de movimientos.
¿Qué piensas? ¿Es esto correcto? ¿Es el movimiento incurrido una pérdida generalmente aceptable en comparación con el beneficio de la reducción del código?
Me interesó su pregunta porque era nuevo en el tema e hice algunas investigaciones. Déjame presentarte los resultados.
Primero, tu suspiro.
:: Suspiros :: Se suponía que C ++ 0x me permitía escribir menos código, ¡no más!
lo que también se supone es darle un mejor control sobre el código. Y eso hace. Me quedaría con un constructor adicional:
OurClass::OurClass(SomeClass&& obj) : obj(std::move(obj)) {}
Personalmente prefiero la verbosidad en situaciones complejas e importantes, porque me mantiene alerta a mí y a los posibles lectores de mi código.
Tomemos, por ejemplo, el cast de estilo C (T*)pT
y el estándar C ++ static_cast<T*>(pT)
Mucho más detallado, pero un gran paso adelante.
En segundo lugar, desconfiaba un poco de su Ejemplo 3, último caso de prueba. Pensé que podría haber otro constructor de movimientos involucrado para crear el parámetro pasado por valor a partir del valor. Así que he creado un proyecto rápido en mi nuevo VS2010 y tengo algunas aclaraciones. Voy a publicar el código aquí, así como los resultados.
la fuente:
// test.cpp : Defines the entry point for the console application.
//
#include "stdafx.h"
#include <utility>
#include <iostream>
class SomeClass{
mutable int *pVal;
public:
int Val() const { return *pVal; };
SomeClass(int val){
pVal = new int(val);
std::cout << "SomeClass constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
}
SomeClass(const SomeClass& r){
pVal = new int(r.Val());
std::cout << "SomeClass copy constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
}
SomeClass(const SomeClass&& r){
pVal = r.pVal;
r.pVal = 0;
std::cout << "SomeClass move constructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
}
~SomeClass(){
if(pVal)
delete pVal;
std::cout << "SomeClass destructor(pVal = 0x" << std::hex << pVal << std::dec << ")" << std::endl;
}
};
class OtherClass{
SomeClass sc;
public:
OtherClass(int val):sc(val){
}
Tenga en cuenta esta sección:
#if 1
OtherClass(SomeClass r):sc(std::move(r)){
}
#else
OtherClass(const SomeClass& r):sc(r){
}
OtherClass(const SomeClass&& r):sc(std::move(r)){
}
#endif
...
int Val(){ return sc.Val(); }
~OtherClass(){
}
};
#define ECHO(expr) std::cout << std::endl << "line " << __LINE__ << ":/t" #expr ":" << std::endl; expr
int _tmain(int argc, _TCHAR* argv[])
{
volatile int __dummy = 0;
ECHO(SomeClass o(10));
ECHO(OtherClass oo1(o));
__dummy += oo1.Val();
ECHO(OtherClass oo2(std::move(o)));
__dummy += oo2.Val();
ECHO(OtherClass oo3(SomeClass(20)));
__dummy += oo3.Val();
ECHO(std::cout << __dummy << std::endl);
ECHO(return 0);
}
Como ha notado, hay un cambio de tiempo de compilación que me permite probar los dos enfoques.
Los resultados se ven mejor en el modo de comparación de texto, a la izquierda puede ver la #if 1
, lo que significa que verificamos la solución propuesta, y a la derecha - #if 0
, lo que significa que verificamos la forma "kosher" descrita en el c ++ 0x!
Me equivoqué al sospechar que el compilador hace estupideces; guardó el constructor de movimiento extra en el tercer caso de prueba.
Pero para ser honestos, tenemos que tener en cuenta otros dos destructores a los que se llama en la solución propuesta, pero esto es sin duda un inconveniente menor que tiene en cuenta que no se deben realizar acciones si se produce un movimiento en el objeto que se está destruyendo. Aún así, es bueno saberlo.
En cualquier caso, no estoy dejando de lado que es mejor escribir otro constructor en la clase contenedora. Es solo una cuestión de varias líneas, ya que todo el trabajo tedioso ya está hecho en SomeClass
que debe tener un constructor de movimientos.