c++ como - ¿La Regla de tres se convierte en Regla de cinco con C ++ 11?





sacar para (8)


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, operador de asignación o destructor probablemente generará errores, mientras que no tener un constructor de movimiento solo reducirá potencialmente el rendimiento.

  • Move constructor 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 controles adicionales para determinar si sus punteros están asignados o se han retirado (ahora son nulos).

Así que, después de ver esta maravillosa conferencia sobre referencias de valores, pensé que todas las clases se beneficiarían de un "constructor de movimientos" de este tipo, la template<class T> MyClass(T&& other) editaría y, por supuesto, un "operador de asignación de movimientos", la template<class T> MyClass& operator=(T&& other) como señala Philipp en su respuesta, si tiene miembros asignados dinámicamente, o generalmente almacena punteros. Al igual que usted debería tener un copy-ctor, operador de asignación y destructor si se aplican los puntos mencionados anteriormente. ¿Pensamientos?




No podemos decir que la regla de 3 se convierte en regla de 4 (o 5) ahora sin romper todo el código existente que hace cumplir la regla de 3 y no implementa ninguna forma de movimiento semántico.

La regla de 3 significa que si implementas uno, debes implementar los 3.

Tampoco es consciente de que habrá algún movimiento autogenerado. El propósito de la "regla de 3" es que existen automáticamente y si implementa una, es muy probable que la implementación predeterminada de las otras dos sea incorrecta.




En el caso general, entonces sí, la regla de tres se convirtió en la de cinco, con el operador de asignación de movimientos y el constructor de movimientos agregados. Sin embargo, no todas las clases son copiables y movibles, algunas son movibles, algunas son simplemente copiables.




No lo creo, la regla de tres es una regla de oro que indica que una clase que implementa uno de los siguientes, pero no todos ellos probablemente tenga errores.

  1. Copia constructor
  2. Operador de asignación
  3. Incinerador de basuras

Sin embargo, dejar de lado el constructor de movimiento o el operador de asignación de movimiento no implica un error. Puede ser una oportunidad perdida en la optimización (en la mayoría de los casos) o que la semántica de movimiento no sea relevante para esta clase, pero esto no es un error.

Si bien puede ser una buena práctica definir un constructor de movimiento cuando sea relevante, no es obligatorio. Hay muchos casos en los que un constructor de movimiento no es relevante para una clase (por ejemplo, std::complex ) y todas las clases que se comportan correctamente en C ++ 03 continuarán comportándose correctamente en C ++ 0x, incluso si no lo hacen. Definir un constructor de movimientos.




Básicamente, es así: si no declara ninguna operación de movimiento, debe respetar la regla de tres. Si declara una operación de movimiento, no hay ningún 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 C ++ 0x le avise 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 un ahora en desuso "regla de compatibilidad C ++ 03".

Creo que es seguro decir que esta regla se vuelve un poco menos significativa. El verdadero problema en C ++ 03 es que la implementación de diferentes semánticas de copia requiere que el usuario declare todas las funciones especiales relacionadas para que ninguna de ellas sea generada por el compilador (que de lo contrario haría lo 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 la 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 con C ++ 03, todavía se generan algunas operaciones, pero esta generación se considera obsoleta y al menos debería producir una advertencia en el modo C ++ 0x.

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

Aquí hay algunos ejemplos que deberían estar bien con las nuevas 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 las operaciones de copia posiblemente generadas por el compilador que podrían hacer algo incorrecto. Aquí, la regla de los 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 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;
};

Por lo tanto, la regla de tres aún se aplica aquí simplemente debido a la compatibilidad con C ++ 03.




Yo diría que la Regla de Tres se convierte en la Regla de Tres, Cuatro y Cinco:

Cada clase debe definir explícitamente exactamente uno de los siguientes conjuntos de funciones especiales para miembros:

  • Ninguna
  • Destructor, copia constructor, operador de asignación de copia

Además, cada clase que define explícitamente un destructor puede definir explícitamente un constructor de movimiento y / o un operador de asignación de movimiento.

Por lo general, uno de los siguientes conjuntos de funciones miembro especiales es sensible:

  • Ninguna (para muchas clases simples donde las funciones de miembro especiales generadas implícitamente son correctas y rápidas)
  • Destructor, constructor de copia, operador de asignación de copia (en este caso, la clase no será movible)
  • Destructor, mover constructor, mover operador de asignación (en este caso, la clase no será copiable, útil para las clases de administración de recursos donde el recurso subyacente no es copiable)
  • Destructor, constructor de copia, operador de asignación de copia, constructor de movimiento (debido a la elección de copia, no hay sobrecarga si el operador de asignación de copia toma su argumento por valor)
  • Destructor, constructor de copia, operador de asignación de copia, constructor de movimiento, operador de asignación de movimiento

Tenga en cuenta que el constructor de movimiento y el operador de asignación de movimiento no se generarán para una clase que declare explícitamente cualquiera de las otras funciones miembro especiales, que el constructor de copia y el operador de asignación de copia no se generarán para una clase que declare explícitamente un constructor o movimiento de movimiento operador de asignación, y que una clase con un destructor declarado explícitamente y un constructor de copia definido implícitamente o un operador de asignación de copia definido implícitamente se considera obsoleta. En particular, la siguiente clase base polimórfica C ++ 03 perfectamente válida

class C {
  virtual ~C() { }   // allow subtype polymorphism
};

debe ser reescrito como sigue:

class C {
  C(const C&) = default;               // Copy constructor
  C(C&&) = default;                    // Move constructor
  C& operator=(const C&) = default;  // Copy assignment operator
  C& operator=(C&&) = default;       // Move assignment operator
  virtual ~C() { }                     // Destructor
};

Un poco molesto, pero probablemente mejor que la alternativa (generación automática de todas las funciones de miembro especiales).

En contraste con la Regla de los Tres Grandes, donde el incumplimiento de la regla puede causar un daño grave, no declarar explícitamente el constructor de movimiento y el operador de asignación de movimiento generalmente está bien, pero a menudo es subóptimo con respecto a la eficiencia. Como se mencionó anteriormente, los operadores de mover constructor y de asignación de movimiento solo se generan si no hay un constructor de copia, operador de asignación de copia o destructor declarado explícitamente. Esto no es simétrico al comportamiento tradicional de C ++ 03 con respecto a la generación automática de constructor de copia y operador de asignación de copia, pero es mucho más seguro. Por lo tanto, la posibilidad de definir constructores de movimiento y operadores de asignación de movimiento es muy útil y crea nuevas posibilidades (clases puramente móviles), pero las clases que se adhieren a la regla C ++ 03 de los Tres Grandes seguirán siendo buenas.

Para las clases de administración de recursos, puede definir el constructor de copia y el operador de asignación de copia como eliminado (lo que cuenta como definición) si el recurso subyacente no se puede copiar. A menudo, aún desea mover el constructor y mover el operador de asignación. Los operadores de asignación de copia y movimiento a menudo se implementarán utilizando swap , como en C ++ 03. Si tiene un constructor de movimientos y un operador de asignación de movimientos, la especialización de std::swap dejará de ser importante porque el genérico std::swap usa el constructor de movimientos y el operador de asignación de movimientos si está disponible, y eso debería ser lo suficientemente rápido.

Las clases que no están destinadas a la administración de recursos (es decir, sin destructor no vacío) o al polimorfismo de subtipo (es decir, sin destructor virtual) no deben declarar ninguna de las cinco funciones miembro especiales; Todos se generarán automáticamente y se comportarán de forma correcta y rápida.




Aquí hay una breve actualización sobre el estado actual y los desarrollos relacionados desde el 24 de enero de 2011.

De acuerdo con la Norma C ++ 11 (ver [depr.impldec] del Anexo D):

La declaración implícita de un constructor de copia está en desuso si la clase tiene un operador de asignación de copia declarado por el usuario o un destructor declarado por el usuario. La declaración implícita de un operador de asignación de copia está en desuso si la clase tiene un constructor de copia declarado por el usuario o un destructor declarado por el usuario.

En realidad, se proposed que el comportamiento obsoleto quedara obsoleto, lo que daba a C ++ 14 una verdadera "regla de cinco" en lugar de la tradicional "regla de tres". En 2013, el EWG votó en contra de esta propuesta para implementarse en C ++ 2014. La razón principal para la decisión sobre la propuesta tenía que ver con las preocupaciones generales sobre la ruptura del código existente.

Recientemente, se ha proposed nuevo adaptar la redacción de C ++ 11 para lograr la Regla de los Cinco informal, a saber, que

ninguna función de copia, función de movimiento o destructor puede ser generada por el compilador si alguna de estas funciones es proporcionada por el usuario.

Si es aprobado por el EWG, es probable que se adopte la "regla" para C ++ 17.




Esta es ahora una pregunta de varios años, pero al ser muy popular, vale la pena mencionar un recurso fantástico para aprender sobre el modelo de memoria C ++ 11. No tengo sentido resumir su charla para hacer que esta sea otra respuesta completa, pero dado que este es el tipo que realmente escribió el estándar, creo que vale la pena ver la charla.

Herb Sutter tiene una charla de tres horas sobre el modelo de memoria C ++ 11 titulado "atomic <> Weapons", disponible en el sitio Channel9, parte 1 y parte 2 . La charla es bastante técnica, y cubre los siguientes temas:

  1. Optimizaciones, razas y el modelo de memoria.
  2. Ordenar - Qué: Adquirir y Liberar
  3. Pedidos: cómo: Mutexes, atómicas y / o cercas
  4. Otras restricciones en compiladores y hardware
  5. Generación de código y rendimiento: x86 / x64, IA64, POWER, ARM
  6. Atomica Relajada

La charla no da más detalles sobre la API, sino sobre el razonamiento, el fondo, bajo el capó y detrás de escena (¿sabía que la semántica relajada se agregó al estándar solo porque POWER y ARM no admiten la carga sincronizada de manera eficiente?).





c++ constructor c++11 rvalue-reference rule-of-three