Significado de = eliminar después de la declaración de función


242
class my_class
{
    ...
    my_class(my_class const &) = delete;
    ...
};

¿Qué = deletesignifica en ese contexto?

¿Hay otros "modificadores" (que no sean = 0y = delete)?


23
@Blindy Será estándar en C ++ 0x, es decir, pronto.
Konrad Rudolph el

1
Estoy corregido, me había perdido esta característica de C ++ 0x. Estaba pensando que era un #definea la Qt que se evaluaba a 0 y luego declaraba una función oculta o algo así.
Blindy 01 de

Recuerdo una palabra clave 'deshabilitar' que significa lo mismo o algo similar. ¿Me lo estoy imaginando? ¿O hay una sutil diferencia entre ellos?
Stewart

Respuestas:


201

Eliminar una función es una característica de C ++ 11 :

El idioma común de "prohibir la copia" ahora se puede expresar directamente:

class X {
    // ...
    X& operator=(const X&) = delete;  // Disallow copying
    X(const X&) = delete;
};

[...]

El mecanismo de "eliminación" se puede utilizar para cualquier función. Por ejemplo, podemos eliminar una conversión no deseada como esta:

struct Z {
    // ...

    Z(long long);     // can initialize with an long long         
    Z(long) = delete; // but not anything less
};

3
¿No es el método tradicional "prohibir la copia" solo para hacer que copy-ctor and operator = "private"? Esto va un poco más allá y le indica al compilador que ni siquiera genere las funciones. Si ambos son privados y = eliminar, ¿la copia está doblemente prohibida?
Reb

8
@Reb, =deletehace que el método sea inaccesible incluso desde contextos que pueden ver privatemétodos (es decir, dentro de la clase y sus amigos). Esto elimina cualquier incertidumbre cuando estás leyendo el código. @Prasoon, ese segundo ejemplo todavía solo está eliminando constructores; sería bueno ver un eliminado, operator long ()por ejemplo.
Toby Speight

2
@ Reb.Cabin Usar = deletees mejor que usar privateu otros mecanismos similares porque generalmente desea que la función prohibida se declare visiblemente y se considere para la resolución de sobrecarga, etc., de modo que pueda fallar lo antes posible y proporcionar el error más claro al usuario. Cualquier solución que implique "ocultar" la declaración reduce este efecto.
Leushenko

1
¿Hay alguna razón especial para hacer público el constructor de copia y aplicar la palabra clave delete. ¿Por qué no dejar el constructor privado y aplicar la palabra clave?
Dohn Joe

81
  1. = 0significa que una función es puramente virtual y no puede crear una instancia de un objeto de esta clase. Necesitas derivar de él e implementar este método
  2. = deletesignifica que el compilador no generará esos constructores para usted. AFAIK esto solo está permitido en el constructor de copia y el operador de asignación. Pero no soy demasiado bueno en el próximo estándar.

44
Hay otros usos de la =deletesintaxis. Por ejemplo, puede usarlo para rechazar explícitamente algún tipo de conversiones implícitas que puedan tener lugar con la llamada. Para esto, simplemente elimine las funciones sobrecargadas. Echa un vistazo a la página de Wikipedia en C ++ 0x para obtener más información.
LiKao 01 de

Lo haré tan pronto como encuentre alguno. Supongo que es hora de ponerse al día con c ++
0X

Sí, rocas C ++ 0x. No puedo esperar a que GCC 4.5+ sea más común, así que puedo comenzar a usar lambdas.
LiKao

55
La descripción de = deleteno es del todo correcta. = deletese puede usar para cualquier función, en cuyo caso se marca explícitamente como eliminado y cualquier uso da como resultado un error del compilador. Para funciones especiales de miembros, esto también significa en particular que el compilador no las genera para usted, pero eso es solo el resultado de ser eliminado y no lo que = deleterealmente es.
MicroVirus

28

Este extracto del lenguaje de programación C ++ [4ª edición] - El libro de Bjarne Stroustrup habla sobre el verdadero propósito de usar =delete:

3.3.4 Operaciones de supresión

Usar la copia o el movimiento predeterminados para una clase en una jerarquía suele ser un desastre : dado solo un puntero a una base, simplemente no sabemos qué miembros tiene la clase derivada, por lo que no podemos saber cómo copiarlos . Por lo tanto, lo mejor que puede hacer es eliminar las operaciones predeterminadas de copiar y mover, es decir, eliminar las definiciones predeterminadas de esas dos operaciones:

class Shape {
public:
  Shape(const Shape&) =delete; // no copy operations
  Shape& operator=(const Shape&) =delete;

  Shape(Shape&&) =delete; // no move operations
  Shape& operator=(Shape&&) =delete;
  ˜Shape();
    // ...
};

Ahora el compilador detectará un intento de copiar una Forma.

El =deletemecanismo es general, es decir, se puede utilizar para suprimir cualquier operación.



5

Los estándares de codificación con los que he trabajado han tenido lo siguiente para la mayoría de las declaraciones de clase.

//  coding standard: disallow when not used
T(void)                  = delete; // default ctor    (1)
~T(void)                 = delete; // default dtor    (2)
T(const T&)              = delete; // copy ctor       (3)
T(const T&&)             = delete; // move ctor       (4)
T& operator= (const T&)  = delete; // copy assignment (5)
T& operator= (const T&&) = delete; // move assignment (6)

Si usa cualquiera de estos 6, simplemente comente la línea correspondiente.

Ejemplo: la clase FizzBus solo requiere dtor y, por lo tanto, no usa los otros 5.

//  coding standard: disallow when not used
FizzBuzz(void)                         = delete; // default ctor (1)
// ~FizzBuzz(void);                              // dtor         (2)
FizzBuzz(const FizzBuzz&)              = delete; // copy ctor    (3)
FizzBuzz& operator= (const FizzBuzz&)  = delete; // copy assig   (4)
FizzBuzz(const FizzBuzz&&)             = delete; // move ctor    (5)
FizzBuzz& operator= (const FizzBuzz&&) = delete; // move assign  (6)

Comentamos solo 1 aquí e instalamos la implementación de él en otro lugar (probablemente donde lo sugiere el estándar de codificación). Los otros 5 (de 6) no están permitidos con eliminar.

También puede usar '= eliminar' para no permitir promociones implícitas de valores de diferentes tamaños ... ejemplo

// disallow implicit promotions 
template <class T> operator T(void)              = delete;
template <class T> Vuint64& operator=  (const T) = delete;
template <class T> Vuint64& operator|= (const T) = delete;
template <class T> Vuint64& operator&= (const T) = delete;

3

= deletees una característica introducida en C ++ 11. Según =deleteno se permitirá llamar a esa función.

En detalle.

Supongamos en una clase.

Class ABC{
 Int d;
 Public:
  ABC& operator= (const ABC& obj) =delete
  {

  }
};

Al llamar a esta función para la asignación de obj, no se permitirá. El operador de asignación de medios va a restringir la copia de un objeto a otro.



1

Una función eliminada está implícitamente en línea

(Anexo a las respuestas existentes)

... Y una función eliminada será la primera declaración de la función (excepto para eliminar especializaciones explícitas de plantillas de funciones; la eliminación debe realizarse en la primera declaración de la especialización), lo que significa que no puede declarar una función y luego eliminarla, por ejemplo, en su definición local a una unidad de traducción.

Citando [dcl.fct.def.delete] / 4 :

Una función eliminada está implícitamente en línea. ( Nota: la regla de una definición ( [basic.def.odr] ) se aplica a las definiciones eliminadas. - Nota final ] Una definición eliminada de una función será la primera declaración de la función o, para una especialización explícita de una plantilla de función , la primera declaración de esa especialización. [Ejemplo:

struct sometype {
  sometype();
};
sometype::sometype() = delete;      // ill-formed; not first declaration

- ejemplo final )

Se puede especializar una plantilla de función primaria con una definición eliminada

Aunque una regla general es evitar las plantillas de funciones especializadas ya que las especializaciones no participan en el primer paso de la resolución de sobrecarga, existen algunos contextos discutibles en los que puede ser útil. Por ejemplo, cuando se utiliza una plantilla de función primaria no sobrecargada sin definición para que coincida con todos los tipos que a uno no le gustaría convertir implícitamente en una sobrecarga de coincidencia por conversión; es decir, eliminar implícitamente varias coincidencias de conversión implícita implementando solo coincidencias de tipo exacto en la especialización explícita de la plantilla de función primaria no definida y no sobrecargada.

Antes del concepto de función eliminado de C ++ 11, uno podía hacer esto simplemente omitiendo la definición de la plantilla de función primaria, pero esto daba oscuros errores de referencia indefinidos que posiblemente no dieron ninguna intención semántica del autor de la plantilla de función primaria (omitido intencionalmente ?) Si, en cambio, eliminamos explícitamente la plantilla de función primaria, los mensajes de error en caso de que no se encuentre una especialización explícita adecuada se vuelven mucho más agradables, y también muestran que la omisión / eliminación de la definición de la plantilla de función primaria fue intencional.

#include <iostream>
#include <string>

template< typename T >
void use_only_explicit_specializations(T t);

template<>
void use_only_explicit_specializations<int>(int t) {
    std::cout << "int: " << t;
}

int main()
{
    const int num = 42;
    const std::string str = "foo";
    use_only_explicit_specializations(num);  // int: 42
    //use_only_explicit_specializations(str); // undefined reference to `void use_only_explicit_specializations< ...
}

Sin embargo, en lugar de simplemente omitir una definición para la plantilla de función primaria anterior, arrojando un oscuro error de referencia indefinido cuando no hay coincidencias de especialización explícitas, la definición de plantilla primaria se puede eliminar:

#include <iostream>
#include <string>

template< typename T >
void use_only_explicit_specializations(T t) = delete;

template<>
void use_only_explicit_specializations<int>(int t) {
    std::cout << "int: " << t;
}

int main()
{
    const int num = 42;
    const std::string str = "foo";
    use_only_explicit_specializations(num);  // int: 42
    use_only_explicit_specializations(str);
    /* error: call to deleted function 'use_only_explicit_specializations' 
       note: candidate function [with T = std::__1::basic_string<char>] has 
       been explicitly deleted
       void use_only_explicit_specializations(T t) = delete; */
}

Produciendo un mensaje de error más legible, donde la intención de eliminación también es claramente visible (donde un error de referencia indefinido podría hacer que el desarrollador piense que es un error irreflexivo).

Volviendo a por qué querríamos usar esta técnica. Una vez más, especializaciones explícitas podrían ser útiles para implícitamente eliminar las conversiones implícitas.

#include <cstdint>
#include <iostream>

void warning_at_best(int8_t num) { 
    std::cout << "I better use -Werror and -pedantic... " << +num << "\n";
}

template< typename T >
void only_for_signed(T t) = delete;

template<>
void only_for_signed<int8_t>(int8_t t) {
    std::cout << "UB safe! 1 byte, " << +t << "\n";
}

template<>
void only_for_signed<int16_t>(int16_t t) {
    std::cout << "UB safe! 2 bytes, " << +t << "\n";
}

int main()
{
    const int8_t a = 42;
    const uint8_t b = 255U;
    const int16_t c = 255;
    const float d = 200.F;

    warning_at_best(a); // 42
    warning_at_best(b); // implementation-defined behaviour, no diagnostic required
    warning_at_best(c); // narrowing, -Wconstant-conversion warning
    warning_at_best(d); // undefined behaviour!

    only_for_signed(a);
    only_for_signed(c);

    //only_for_signed(b);  
    /* error: call to deleted function 'only_for_signed' 
       note: candidate function [with T = unsigned char] 
             has been explicitly deleted
       void only_for_signed(T t) = delete; */

    //only_for_signed(d);
    /* error: call to deleted function 'only_for_signed' 
       note: candidate function [with T = float] 
             has been explicitly deleted
       void only_for_signed(T t) = delete; */
}

0

Esto es algo nuevo en los estándares C ++ 0x donde puede eliminar una función heredada.


11
Puedes borrar cualquier función. Por ejemplo, void foo(int); template <class T> void foo(T) = delete;detiene todas las conversiones implícitas. Solo intse aceptan argumentos de tipo, todos los demás intentarán instanciar una función "eliminada".
UncleBens 01 de
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.