¿La regla de Tres se convierte en Regla de Cinco con C++11?


Author: Flexo, 2011-01-24

8 answers

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 una de la siguiente serie de miembros especiales funciones:

  • Ninguno
  • Destructor, constructor de copia, 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 miembros especiales funciones es sensible:

  • Ninguno (para muchas clases simples donde las funciones miembro especiales generadas implícitamente son correctas y rápidas)
  • Destructor, constructor de copia, operador de asignación de copia (en este caso el la clase no será móvil)
  • Destructor, move constructor, move assignment operator (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, copy constructor, copy assignment operator, move constructor (debido a copy elision, no hay sobrecarga si el copy assignment operator toma su argumento por valor)
  • Destructor, constructor de copia, operador de asignación de copia, constructor de movimiento, mover operador de asignación

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 a cualquiera de los otros miembros especiales funciones, 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 de movimiento o un operador de asignación de movimiento, 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 considere obsoleta. En particular, la siguiente clase base polimórfica perfectamente válida de C++03

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

Debe reescribirse 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 miembro especiales).

En contraste con la Regla de los Tres Grandes, donde no adherirse a la regla puede causar daños graves, no declarar explícitamente el constructor de movimiento y el operador de asignación de movimiento es generalmente correcto, pero a menudo subóptimo con respecto a la eficiencia. Como se mencionó anteriormente, los operadores move constructor y move assignment solo se generan si no hay un constructor de copia declarado explícitamente, copy operador de asignación o destructor. 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 de C++03 de los Tres Grandes seguirán estando bien.

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

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

 296
Author: Philipp,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-03 00:40:01

No puedo creer que nadie esté vinculado a esto.

Básicamente el artículo defiende la "Regla de Cero". No es apropiado para mí citar todo el artículo, 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 deben tener destructores personalizados, copiar / mover constructores u operadores de asignación copiar / mover.

También este bit es importante en MI humilde opinión:

Las clases comunes de "propiedad en un paquete" se incluyen en el estándar biblioteca: std::unique_ptr y std::shared_ptr. Mediante el uso de objetos deleter personalizados, ambos se han hecho lo suficientemente flexibles para administrar prácticamente cualquier tipo de recurso.

 61
Author: NoSenseEtAl,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2017-07-04 03:08:31

No lo creo, la regla de tres es una regla empírica que establece que una clase que implementa una de las siguientes pero no todas es probablemente defectuosa.

  1. Copy constructor
  2. Operador de asignación
  3. Destructor

Sin embargo, omitir 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 es relevante para esta clase, pero esto no es un micrófono.

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 move 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 definen un constructor move.

 18
Author: Motti,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-24 14:56:36

Sí, creo que sería bueno proporcionar un constructor de movimiento para tales clases, pero recuerde que:

  • Es solo una optimización.

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

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

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

 14
Author: peoro,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2013-02-01 13:29:46

Aquí hay una breve actualización sobre el estado actual y los acontecimientos relacionados desde Jan 24 '11.

De acuerdo con el estándar C++11 (véase el Anexo D [depr.impldec]):

La declaración implícita de un constructor de copia es obsoleta 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á obsoleta si la clase tiene un constructor de copia declarado por el usuario o un constructor declarado por el usuario destructor.

En realidad propuso que el comportamiento obsoleto diera 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 ser implementada en C++2014. La razón principal de la decisión sobre la propuesta tenía que ver con la preocupación general por romper el código existente.

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

No se generará ninguna función de copia, función de movimiento o destructor si alguna de estas funciones es proporcionada por el usuario.

Si es aprobada por el EWG, es probable que la "regla" sea adoptada para C++17.

 8
Author: Andrey Rekalo,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2015-01-11 19:17:23

Básicamente, es así: Si no declaras ninguna operación de movimiento, debes respetar la regla de tres. Si declara una operación move, 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 dé una advertencia en caso de que una función especial haya sido declarada por el usuario y otras funciones especiales hayan sido auto-generado debido a una ahora obsoleta "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 (lo que de otra manera haría lo incorrecto). Pero C++0x cambia las reglas sobre la generación de funciones miembro especiales. Si el usuario declara solo uno de estos 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 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 permanece como la regla de tres.

Aquí hay algunos ejemplos que deberían estar bien con las reglas más recientes 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 hay necesidad de declarar ninguna de las otras funciones especiales como eliminada. 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 produce una advertencia sobre posibles operaciones de copia generadas por el compilador que podrían hacer lo incorrecto. Aquí, la regla de tres asuntos y debe respetarse. 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 todavía se aplica aquí simplemente debido a la compatibilidad de C++03.

 4
Author: sellibitze,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-24 20:14:41

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 semántica de movimiento.

La regla de 3 significa que si implementas una debes implementar todas las 3.

Tampoco sabe que habrá ningún movimiento generado automáticamente. El propósito de la" regla de 3 " es porque existen automáticamente y si implementas una, lo más probable es que la implementación predeterminada de las otras dos sea incorrecta.

 3
Author: CashCow,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-24 14:07:37

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

 2
Author: Puppy,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-01-24 14:16:09