c++ - ¿Cuáles son las ventajas de boost:: noncopyable
(11)
Para evitar copiar una clase, puede declarar fácilmente un operador de asignación / constructor de copia privada. Pero también puedes heredar boost::noncopyable
.
¿Cuáles son las ventajas / desventajas de usar boost en este caso?
- La intención de boost :: noncopyable es más clara.
- Boost :: noncopyable impide que los métodos de clases accidentalmente utilicen el constructor de copia privada.
- Menos código con boost :: noncopyable.
Citando la documentación:
"La forma tradicional de lidiar con esto es declarar un copiador privado y copiar la asignación, y luego documentar por qué se hace esto. Pero derivar de no copiable es más simple y claro, y no requiere documentación adicional".
http://www.boost.org/libs/utility/utility.htm#Class_noncopyable
Hace que el intento sea explícito y claro ; de lo contrario, uno debe ver la definición de la clase y buscar la declaración relacionada con la semántica de copia, y luego buscar el especificador de acceso en el que se declara , para determinar si el la clase no se puede copiar o no. Otra forma de descubrirlo es escribir código que requiera copia semántica habilitada y ver el error de compilación.
La desventaja, de acuerdo con Scott Meyers, el nombre es "no natrual", si necesita encontrar una desventaja de él.
La ventaja es que no tiene que escribir usted mismo un constructor de copia privada y un operador de copia privada y expresa claramente su intención sin escribir documentación adicional.
No puedo entender por qué nadie más parece mencionarlo, pero:
Con noncopyable
escribe el nombre de tu clase solo una vez.
Sin, la duplicación de cinco veces : una A para ''clase A'', dos para deshabilitar la asignación y dos para deshabilitar el constructor de copia.
No veo ningún beneficio de documentación:
#include <boost/noncopyable.hpp>
struct A
: private boost::noncopyable
{
};
vs:
struct A
{
A(const A&) = delete;
A& operator=(const A&) = delete;
};
Cuando agrega tipos de solo movimiento, incluso veo la documentación como engañosa. Los siguientes dos ejemplos no se pueden copiar, aunque son móviles:
#include <boost/noncopyable.hpp>
struct A
: private boost::noncopyable
{
A(A&&) = default;
A& operator=(A&&) = default;
};
vs:
struct A
{
A(A&&) = default;
A& operator=(A&&) = default;
};
En herencia múltiple, incluso puede haber una penalización de espacio:
#include <boost/noncopyable.hpp>
struct A
: private boost::noncopyable
{
};
struct B
: public A
{
B();
B(const B&);
B& operator=(const B&);
};
struct C
: public A
{
};
struct D
: public B,
public C,
private boost::noncopyable
{
};
#include <iostream>
int main()
{
std::cout << sizeof(D) << ''/n'';
}
Para mí esto se imprime:
3
Pero esto, que creo que tiene una documentación superior:
struct A
{
A(const A&) = delete;
A& operator=(const A&) = delete;
};
struct B
: public A
{
B();
B(const B&);
B& operator=(const B&);
};
struct C
: public A
{
C(const C&) = delete;
C& operator=(const C&) = delete;
};
struct D
: public B,
public C
{
D(const D&) = delete;
D& operator=(const D&) = delete;
};
#include <iostream>
int main()
{
std::cout << sizeof(D) << ''/n'';
}
Productos:
2
Me resulta mucho más fácil declarar mis operaciones de copia que razonar si estoy derivado de boost::non_copyable
varias veces y si eso me va a costar. Especialmente si no soy el autor de la jerarquía de herencia completa.
Prefiero usar boost :: noncopyable que eliminar manualmente o privatizar el constructor de copias y el operador de asignación.
Sin embargo, casi nunca uso ninguno de los métodos, porque:
Si estoy creando un objeto no copiable, tiene que haber un motivo por el que no se pueda copiar. Esta razón, el 99% de las veces, se debe a que tengo miembros que no se pueden copiar de manera significativa. Es probable que dichos miembros sean más adecuados como detalles privados de implementación. Así que hago la mayoría de las clases de este tipo:
struct Whatever {
Whatever();
~Whatever();
private:
struct Detail;
std::unique_ptr<Detail> detail;
};
Así que ahora, tengo una estructura de implementación privada, y como he usado std :: unique_ptr, mi clase de nivel superior no se puede copiar de forma gratuita. Los errores de enlace que surgen de esto son comprensibles porque hablan sobre cómo no se puede copiar un std :: unique_ptr. Para mí, esto son todos los beneficios de boost :: noncopyable y una implementación privada, todo en uno.
El beneficio de este patrón es posterior, si decido que sí quiero hacer que mis objetos de esta clase se puedan copiar, solo puedo agregar e implementar un constructor de copias y / o un operador de asignación sin cambiar la jerarquía de clases.
Resumiendo lo que otros han dicho:
Ventajas de boost::noncopyable
sobre métodos de copia privada :
- Es más explícito y descriptivo en la intención. El uso de funciones de copia privada es un modismo que tarda más en detectarse que no
noncopyable
. - Es menos código / menos tipeo / menos desorden / menos margen de error (el más fácil sería proporcionar accidentalmente una implementación).
- Incorpora significado directamente en los metadatos del tipo, similar a un atributo C #. Ahora puede escribir una función que solo acepta objetos que no se pueden copiar.
- Posiblemente detecta errores más temprano en el proceso de compilación. El error se presentará en tiempo de compilación en lugar de tiempo de enlace, en el caso de que la clase o los amigos de la clase estén haciendo la copia errónea.
- (casi lo mismo que # 4) Impide que la clase misma o los amigos de la clase invoquen los métodos de copia privada.
Ventajas de los métodos de copia privada sobre boost::noncopyable
:
- Sin dependencia de impulso
Una pequeña desventaja (específica de GCC) es que, si compila su programa con g++ -Weffc++
y tiene clases que contienen punteros, por ejemplo
class C : boost::noncopyable
{
public:
C() : p(nullptr) {}
private:
int *p;
};
GCC no entiende lo que está sucediendo:
advertencia: ''clase C'' tiene miembros de datos de puntero [-Weffc ++]
advertencia: pero no anula ''C (const S &)'' [-Weffc ++]
advertencia: o ''operator = (const C &)'' [-Weffc ++]
Si bien no se quejará con:
#define DISALLOW_COPY_AND_ASSIGN(Class) /
Class(const Class &) = delete; /
Class &operator=(const Class &) = delete
class C
{
public:
C() : p(nullptr) {}
DISALLOW_COPY_AND_ASSIGN(C);
private:
int *p;
};
PD Sé que GCC''s -Weffc ++ tiene varios problemas. El código que busca "problemas" es bastante simple, de todos modos ... a veces ayuda.
Una ventaja concreta (más allá de expresar su intención un poco más claramente) es que el error se detectará antes, en la etapa de compilación y no en la etapa de enlace, si una función miembro o amigo intenta copiar un objeto. El constructor / asignación de la clase base no es accesible en ningún lado, dando un error de compilación.
También le impide definir accidentalmente las funciones (es decir, escribir {}
lugar de ;
), un pequeño error que bien puede pasar desapercibido, pero que luego permitiría a los miembros y amigos realizar copias no válidas del objeto.