[c++] ¿Regla de los Tres se convierte en Regla de los Cinco con C ++ 11?



Answers

No puedo creer que nadie se vinculó a this .

Básicamente, el artículo defiende la "Regla del cero". No es apropiado para mí citar un artículo completo, pero creo que este es el punto principal:

Las clases que tienen destructores personalizados, constructores de copia / movimiento o operadores de asignación de copia / movimiento deben tratar exclusivamente con la propiedad. Otras clases no deberían tener destructores personalizados, copiar / mover constructores o copiar / mover operadores de asignación.

También este bit es IMHO importante:

Las clases comunes "ownership-in-a-package" se incluyen en la biblioteca estándar: std::unique_ptr y std::shared_ptr . Mediante el uso de objetos deleter personalizados, ambos se han hecho lo suficientemente flexibles como para administrar prácticamente cualquier tipo de recurso.

Question

Entonces, después de ver esta maravillosa conferencia sobre referencias de rvalue, pensé que cada clase se beneficiaría de un editor de "movimiento constructor", template<class T> MyClass(T&& other) y por supuesto un "operador de asignación de movimiento", template<class T> MyClass& operator=(T&& other) como señala Philipp en su respuesta, si tiene miembros dinámicamente asignados, o generalmente almacena punteros. Al igual que debería tener un copiador, operador de asignación y destructor si se aplican los puntos mencionados anteriormente. ¿Pensamientos?




En el caso general, entonces sí, la regla de tres acaba de convertirse en la de cinco, con el operador de asignación de movimiento y el constructor de movimiento agregados. Sin embargo, no todas las clases son copiables y movibles, algunas solo son movibles, otras solo se pueden copiar.




Básicamente, es así: si no declaras ninguna operación de movimiento, debes respetar la regla de tres. Si declara una operación de movimiento, no hay daño en "violar" la regla de tres ya que la generación de operaciones generadas por el compilador se ha vuelto muy restrictiva. Incluso si no declara operaciones de movimiento y viola la regla de tres, se espera que un compilador de C ++ 0x le dé una advertencia en caso de que una función especial haya sido declarada por el usuario y otras funciones especiales se hayan generado automáticamente debido a una ahora está en desuso "regla de compatibilidad C ++ 03".

Creo que es seguro decir que esta regla se vuelve un poco menos significativa. El problema real en C ++ 03 es que la implementación de una semántica de copia diferente requiere que declares al usuario todas las funciones especiales relacionadas para que ninguna de ellas sea generada por el compilador (lo que de otro modo haría algo incorrecto). Pero C ++ 0x cambia las reglas sobre la generación de funciones de miembros especiales. Si el usuario declara solo una de estas funciones para cambiar la semántica de copia, evitará que el compilador genere automáticamente las funciones especiales restantes. Esto es bueno porque una declaración faltante convierte un error de tiempo de ejecución en un error de compilación ahora (o al menos una advertencia). Como medida de compatibilidad de C ++ 03, algunas operaciones aún se generan, pero esta generación se considera obsoleta y debería al menos producir una advertencia en el modo C ++ 0x.

Debido a las reglas bastante restrictivas acerca de las funciones especiales generadas por el compilador y la compatibilidad con C ++ 03, la regla de tres permanece como la regla de tres.

Aquí hay algunos exaples que deberían estar bien con las últimas reglas de C ++ 0x:

template<class T>
class unique_ptr
{
   T* ptr;
public:
   explicit unique_ptr(T* p=0) : ptr(p) {}
   ~unique_ptr();
   unique_ptr(unique_ptr&&);
   unique_ptr& operator=(unique_ptr&&);
};

En el ejemplo anterior, no es necesario declarar ninguna de las otras funciones especiales como eliminadas. Simplemente no se generarán debido a las reglas restrictivas. La presencia de operaciones de movimiento declaradas por el usuario deshabilita las operaciones de copia generadas por el compilador. Pero en un caso como este:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
};

ahora se espera que un compilador C ++ 0x produzca una advertencia sobre posibles operaciones de copia generadas por el compilador que podrían hacer algo incorrecto. Aquí, la regla de tres asuntos y debe ser respetada. Una advertencia en este caso es totalmente apropiada y le da al usuario la oportunidad de manejar el error. Podemos deshacernos del problema a través de las funciones eliminadas:

template<class T>
class scoped_ptr
{
   T* ptr;
public:
   explicit scoped_ptr(T* p=0) : ptr(p) {}
   ~scoped_ptr();
   scoped_ptr(scoped_ptr const&) = delete;
   scoped_ptr& operator=(scoped_ptr const&) = delete;
};

Entonces, la regla de tres todavía se aplica aquí simplemente debido a la compatibilidad con C ++ 03.




Sí, creo que sería bueno proporcionar un constructor de movimientos para dichas clases, pero recuerda que:

  • Es solo una optimización.

    Implementar solo uno o dos del constructor de copia, el operador de asignación o el destructor probablemente dará lugar a errores, mientras que no tener un constructor de movimiento reducirá el rendimiento.

  • El constructor Move no siempre se puede aplicar sin modificaciones.

    Algunas clases siempre tienen sus punteros asignados, y por lo tanto, tales clases siempre eliminan sus punteros en el destructor. En estos casos, deberá agregar comprobaciones adicionales para indicar si sus punteros están asignados o si se han movido (ahora son nulos).




Links