Regla de tres (C++)

La versión actual de la página aún no ha sido revisada por colaboradores experimentados y puede diferir significativamente de la versión revisada el 7 de abril de 2022; las comprobaciones requieren 2 ediciones .

La Regla de los Tres (también conocida como la "Ley de los Tres Grandes" o "Tres Grandes") es una regla de C++ que dice que si una clase o estructura define uno de los siguientes métodos, debe definir explícitamente los tres métodos [1 ] :

Estos tres métodos son funciones miembro especiales que el compilador crea automáticamente si el programador no las declara explícitamente. Si uno de ellos debe ser definido por el programador, esto significa que la versión generada por el compilador no satisface las necesidades de la clase en un caso, y probablemente no lo hará en otros casos.

Una enmienda a esta regla es que si se utiliza RAII (del inglés  Resource Acquisition Is Initialization ), el destructor utilizado puede permanecer sin definir (a veces denominado "Ley de los dos grandes") [2] .

Dado que los constructores y operadores de asignación definidos implícitamente simplemente copian todos los miembros de datos de una clase [3] , es necesario definir constructores de copia y operadores de asignación de copia explícitos en los casos en que una clase encapsula estructuras de datos complejas o puede admitir acceso exclusivo a recursos. Y también en los casos en que la clase contenga datos constantes o referencias.

Regla de cinco

Con el lanzamiento del undécimo estándar , la regla se expandió y se conoció como la regla de cinco. Ahora, al implementar el constructor, debe implementar:

Ejemplo de regla de cinco:

#incluir <ccadena> clase RFive { privado : char * ccadena ; público : // Constructor con lista de inicialización y cuerpo RFive ( const char * arg ) : cstring ( nuevo carácter [ estándar :: strlen ( arg ) + 1 ]) { std :: strcpy ( cstring , arg ); } // Destructor ~ RFive () { eliminar [] cstring ; } // Copiar constructor RFive ( const RFive y otros ) { cstring = new char [ std :: strlen ( otro . cstring ) + 1 ]; std :: strcpy ( cstring , otro . cstring ); } // Mover constructor, noexcept - para optimización cuando se usan contenedores estándar RFive ( RFive && otro ) no excepto { cstring = otro . ccadena ; otro _ cstring = nullptr ; } // Copiar operador de asignación RFive & operador = ( const RFive & otro ) { si ( este == y otros ) devolver * esto ; char * tmp_cstring = new char [ std :: strlen ( otro . cstring ) + 1 ]; std :: strcpy ( tmp_cstring , otro . cstring ); eliminar [] cstring ; cstring = tmp_cstring ; devolver * esto ; } // Mover operador de asignación RFive & operador = ( RFive && otro ) no excepto { si ( este == y otros ) devolver * esto ; eliminar [] cstring ; cstring = otro . ccadena ; otro _ cstring = nullptr ; devolver * esto ; } // También puede reemplazar ambas declaraciones de asignación con la siguiente declaración // RFive& operator=(RFive other) // { // std::swap(cstring, otro.cstring); // devuelve *esto; // } };

Copiar y compartir modismos

Siempre debes evitar duplicar el mismo código, porque si cambias o arreglas una sección, tendrás que acordarte de arreglar el resto. El idioma de copiar e intercambiar le permite evitar esto al  reutilizar el código del constructor de copia, por lo que para la clase RFive tendrá que crear una función de intercambio amigable e implementar el operador de asignación copiándolo y moviéndolo. Además, con esta implementación, no hay necesidad de verificar la autoasignación.

#incluir <ccadena> clase RFive { // resto del código RFive & operator = ( const RFive & other ) // Operador de asignación de copias { Rcinco tmp ( otro ); intercambiar ( * esto , tmp ); devolver * esto ; } RFive & operator = ( RFive && other ) // Mover operador de asignación { intercambiar ( * esto , otro ); devolver * esto ; } intercambio de anulación de amigos ( RFive & l , RFive & r ) { utilizando std :: intercambio ; intercambio ( l . cstring , r . cstring ); } // resto del código };

También es apropiado que los operadores de asignación hagan del valor de retorno una referencia constante: const RFive& operator=(const RFive& other);. La constante adicional evitará que escribamos código ofuscado como este: (a=b=c).foo();.

Regla Cero

Martín Fernández también propuso la regla del cero. [5] Según esta regla, no debe definir ninguna de las cinco funciones usted mismo; es necesario confiar su creación al compilador (para asignarles el valor = default;). Para poseer recursos, en lugar de punteros simples, debe usar clases contenedoras especiales, como std::unique_ptry std::shared_ptr. [6]

Enlaces

  1. Bjarne Stroustrup . El lenguaje de programación C++  (neopr.) . - 3. - Addison-Wesley , 2000. - S. 283-284. - ISBN 978-0201700732 .
  2. Karlsson, Bjorn; Wilson, Mateo. La Ley de los Dos Grandes . La fuente de C++ . Artima (1 de octubre de 2004). Fecha de acceso: 22 de enero de 2008. Archivado desde el original el 17 de marzo de 2012.
  3. El lenguaje de programación C++  . - art. 271.
  4. Mover operador de asignación . Es.CPPReference.com . Fecha de acceso: 22 de diciembre de 2014. Archivado desde el original el 23 de diciembre de 2014.
  5. Regla del Cero . Zona de peligro llameante . Consultado el 29 de julio de 2015. Archivado desde el original el 29 de agosto de 2015.
  6. Kulikov Alejandro. Regla 3, 5, 0 Habrahabr . Consultado el 14 de febrero de 2016. Archivado desde el original el 22 de febrero de 2016.