c++
¿El lenguaje de copia e intercambio sigue siendo útil en C++ 11? (4)
Me refiero a esta pregunta: ¿Qué es el lenguaje de copia e intercambio?
Efectivamente, la respuesta anterior conduce a la siguiente implementación:
class MyClass
{
public:
friend void swap(MyClass & lhs, MyClass & rhs) noexcept;
MyClass() { /* to implement */ };
virtual ~MyClass() { /* to implement */ };
MyClass(const MyClass & rhs) { /* to implement */ }
MyClass(MyClass && rhs) : MyClass() { swap(*this, rhs); }
MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }
};
void swap( MyClass & lhs, MyClass & rhs )
{
using std::swap;
/* to implement */
//swap(rhs.x, lhs.x);
}
Sin embargo, observe que podríamos evitar el swap () por completo, haciendo lo siguiente:
class MyClass
{
public:
MyClass() { /* to implement */ };
virtual ~MyClass() { /* to implement */ };
MyClass(const MyClass & rhs) { /* to implement */ }
MyClass(MyClass && rhs) : MyClass() { *this = std::forward<MyClass>(rhs); }
MyClass & operator=(MyClass rhs)
{
/* put swap code here */
using std::swap;
/* to implement */
//swap(rhs.x, lhs.x);
// :::
return *this;
}
};
Tenga en cuenta que esto significa que ya no tendremos una búsqueda válida dependiente del argumento en std :: swap con MyClass.
En resumen, hay alguna ventaja de tener el método swap ().
editar:
Me di cuenta de que hay un error terrible en la segunda implementación anterior, y es algo muy grande, así que lo dejaré como está para instruir a cualquiera que se encuentre con esto.
si operador = se define como
MyClass2 & operator=(MyClass2 rhs)
Entonces, cuando rhs sea un valor r, se llamará al constructor de movimientos. Sin embargo, esto significa que al usar:
MyClass2(MyClass2 && rhs)
{
//*this = std::move(rhs);
}
Observe que termina con una llamada recursiva al constructor de movimientos, ya que operator = llama al constructor de movimientos ...
Esto es muy sutil y difícil de detectar hasta que se obtiene un desbordamiento de la pila en tiempo de ejecución.
Ahora la solución a eso sería tener ambos
MyClass2 & operator=(const MyClass2 &rhs)
MyClass2 & operator=(MyClass2 && rhs)
Esto nos permite definir los constructores de copia como
MyClass2(const MyClass2 & rhs)
{
operator=( rhs );
}
MyClass2(MyClass2 && rhs)
{
operator=( std::move(rhs) );
}
Observe que escribe la misma cantidad de código, los constructores de copia vienen "de forma gratuita" y simplemente escriben operator = (&) en lugar del constructor de copia y operator = (&&) en lugar del método swap ().
Ciertamente no estás considerando el cuadro completo. Su código reutiliza constructores para diferentes operadores de asignación, el original reutiliza operadores de asignación para diferentes constructores. Esto es básicamente lo mismo, todo lo que has hecho es cambiarlo.
Excepto que, dado que escriben constructores, pueden lidiar con tipos no construibles por defecto o cuyos valores son malos si no se inicializan explícitamente como int
o son simplemente costosos de construir por defecto o donde los miembros construidos por defecto no son válidos para destruir ( por ejemplo, considere un puntero inteligente (un T * no inicializado conduce a una eliminación incorrecta).
Básicamente, todo lo que has logrado es el mismo principio pero en un lugar decididamente peor. Ah, y tenía que definir las cuatro funciones, o bien la recursión mutua, mientras que la copia y el intercambio originales solo definían tres funciones.
En primer lugar, lo estás haciendo mal de todos modos. El lenguaje de copiar e intercambiar está allí para reutilizar el constructor para el operador de asignación (y no al revés), aprovechando el código de constructor que ya está construyendo correctamente y garantizando una fuerte seguridad de excepción para el operador de asignación. Pero no se llama swap en el constructor de movimientos. De la misma manera que el constructor de copia copia todos los datos (lo que sea que signifique en el contexto dado de una clase individual), el constructor de movimientos mueve estos datos, el constructor de movimientos construye y asigna / intercambia:
MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { swap(*this, rhs); return *this; }
Y esto sería en tu versión alternativa solo sería
MyClass(const MyClass & rhs) : x(rhs.x) {}
MyClass(MyClass && rhs) : x(std::move(rhs.x)) {}
MyClass & operator=(MyClass rhs) { using std::swap; swap(x, rhs.x); return *this; }
Lo que no presenta el error grave introducido al llamar al operador de asignación dentro del constructor. Nunca debe llamar al operador de asignación o intercambiar todo el objeto dentro de un constructor. Los constructores están ahí para cuidar la construcción y tienen la ventaja de no tener que cuidar la destrucción de los datos anteriores, ya que esos datos aún no existen. Del mismo modo, los constructores pueden manejar tipos no construibles por defecto y, por último, pero no menos importante, la construcción directa puede ser más eficaz que la construcción por defecto seguida de asignación / intercambio.
Pero para responder a su pregunta, todo esto sigue siendo el lenguaje de copia e intercambio, solo que sin una función de swap
explícita. Y en C ++ 11 es aún más útil porque ahora ha implementado la tarea de copiar y mover con una sola función.
Si la función de intercambio todavía tiene valor fuera del operador de asignación, es una pregunta completamente diferente y depende de si es probable que este tipo sea intercambiado, de todos modos. De hecho, en C ++ 11, los tipos con semántica de movimiento adecuada pueden intercambiarse de manera suficientemente eficiente utilizando la implementación estándar std::swap
, lo que a menudo elimina la necesidad de un intercambio personalizado adicional. Solo asegúrese de no llamar a este estándar std::swap
dentro de su operador de asignación, ya que realiza una asignación de movimiento en sí misma (lo que llevaría a los mismos problemas que su implementación incorrecta del constructor de movimientos).
Pero para decirlo de nuevo, función de swap
personalizado o no, esto no cambia nada en la utilidad del lenguaje de copia e intercambio, que es aún más útil en C ++ 11, eliminando la necesidad de implementar una función adicional.
La validez de las razones (si las hay) para usar el lenguaje de copia e intercambio para implementar la asignación de copia es la misma en C ++ 11 que en versiones anteriores.
También tenga en cuenta que debe usar std::move
en las variables miembro en el constructor de movimiento, y debe usar std::move
en cualquier referencia de valor que sea un parámetro de función.
std::forward
solo se debe usar para referencias de parámetros de plantilla del formulario T&&
y también para auto&&
variables auto&&
(que pueden estar sujetas a la inclusión de referencias en valores de lvalue durante la deducción de tipo) para preservar su valor o valor según sea apropiado.
para mebest hazlo así en C ++ 11:
class MyClass {
public:
MyClass(MyString&& pStr) : str(pStr)) {
// call MyString(MyString&& pStr)
}
MyClass(const MyString& pStr) : str(pStr)) {
// call MyString(const MyString& pStr)
}
private:
MyString const str; // Still const!
};