c++11 - sobre - C++ 11 Const referencia VS movimiento semántica
garantias individuales libertad de transito (1)
Me preguntaba en qué situaciones aún necesito usar referencias const en parámetros desde C ++ 11. No entiendo completamente la semántica de movimientos, pero creo que esta es una pregunta legítima. Esta pregunta está destinada solo a las situaciones en las que una referencia constante reemplaza una copia que se está realizando, mientras que solo se necesita para "leer" el valor (por ejemplo, el uso de funciones de miembros const).
Normalmente escribiría una función (miembro) como esta:
#include <vector>
template<class T>
class Vector {
std::vector<T> _impl;
public:
void add(const T& value) {
_impl.push_back(value);
}
};
Pero estoy pensando que es seguro asumir que el compilador lo optimizaría usando la semántica de movimiento si lo escribo así y la class T
supuesto, implementa un constructor de movimiento:
#include <vector>
template<class T>
class Vector {
std::vector<T> _impl;
public:
void add(T value) {
_impl.push_back(value);
}
};
Estoy en lo cierto Si es así, ¿es seguro asumir que se puede usar en cualquier situación? Si no, me gustaría saber cuál. Esto haría la vida mucho más fácil ya que no tendría que implementar una especialización de clase para los tipos fundamentales, por ejemplo, además parece mucho más limpio.
La solución que propones:
void add(T value) {
_impl.push_back(value);
}
Requeriría algún ajuste, ya que de esta manera siempre terminará realizando una copia de value
, incluso si pasa un valor para add()
(dos copias si pasa un value
): ya que el value
es un value
l, el compilador no push_back
automáticamente de él cuando lo pases como argumento a push_back
.
En su lugar, debe hacer esto:
void add(T value) {
_impl.push_back(std::move(value));
// ^^^^^^^^^
}
Esto es mejor, pero aún no es lo suficientemente bueno para el código de la plantilla, porque no sabe si T
es barato o costoso moverse. Si T
es un POD como este:
struct X
{
double x;
int i;
char arr[255];
};
Luego, moverlo no será más rápido que copiarlo (de hecho, moverlo sería lo mismo que copiarlo). Debido a que se supone que su código genérico evita operaciones innecesarias (y eso es porque esas operaciones pueden ser costosas para algunos tipos), no puede permitirse tomar el parámetro por valor.
Una posible solución (la adoptada por la biblioteca estándar de C ++) es proporcionar dos sobrecargas de add()
, una tomando una referencia de valor lime y una tomando una referencia de valor de r:
void add(T const& val) { _impl.push_back(val); }
void add(T&& val) { _impl.push_back(std::move(val)); }
Otra posibilidad es proporcionar una versión de add()
plantilla de reenvío perfecto (posiblemente restringida por SFINAE add()
que acepte una llamada referencia universal (término no estándar acuñado por Scott Meyers):
template<typename U>
void add(U&& val) { _impl.push_back(std::forward<U>(val)); }
Ambas soluciones son óptimas en el sentido de que solo se realiza una copia cuando se proporcionan valores l, y solo se realiza un movimiento cuando se proporcionan valores r.