Significato di = cancella dopo la dichiarazione di funzione


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

Cosa = deletesignifica in quel contesto?

Ci sono altri "modificatori" (diversi da = 0e = delete)?


23
@Blindy Sarà standard in C ++ 0x, cioè presto.
Konrad Rudolph,

1
Sono corretto, avevo perso questa funzionalità C ++ 0x. Stavo pensando che fosse un #definea la Qt che valesse 0 e poi dichiarasse una funzione nascosta o qualcosa del genere.
Blindy,

Ho un ricordo di una parola chiave "disabilita" che significa lo stesso o qualcosa di simile. Lo sto immaginando? O c'è una sottile differenza tra loro?
Stewart,

Risposte:


201

L'eliminazione di una funzione è una funzionalità di C ++ 11 :

Il linguaggio comune di "proibire la copia" ora può essere espresso direttamente:

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

[...]

Il meccanismo "cancella" può essere utilizzato per qualsiasi funzione. Ad esempio, possiamo eliminare una conversione indesiderata come questa:

struct Z {
    // ...

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

3
Il metodo tradizionale per "vietare la copia" non è solo quello di rendere il copy-ctor e l'operatore = "privati?" Questo va un po 'oltre e indica al compilatore di non generare nemmeno le funzioni. Se sono entrambi privati ​​e = delete, la copia è doppiamente vietata?
Reb.Cabin

8
@Reb, =deleterende il metodo inaccessibile anche da contesti che possono vedere privatemetodi (cioè all'interno della classe e dei suoi amici). Questo rimuove qualsiasi incertezza durante la lettura del codice. @Prasoon, quel secondo esempio sta ancora cancellando solo i costruttori - sarebbe bello vedere un cancellato operator long ()per esempio.
Toby Speight,

2
@ Reb.Cabin Usare = deleteè meglio dell'uso privateo di altri meccanismi simili perché di solito si desidera che la funzione proibita sia dichiarata e considerata visibilmente per la risoluzione del sovraccarico, ecc., In modo che possa fallire il prima possibile e fornire all'utente l'errore più chiaro. Qualsiasi soluzione che comporta "nascondere" la dichiarazione riduce questo effetto.
Leushenko,

1
C'è un motivo speciale per rendere pubblico il costruttore della copia e applicare la parola chiave delete. Perché non lasciare il costruttore privato e applicare la parola chiave?
Dohn Joe,

81
  1. = 0significa che una funzione è pura virtuale e non è possibile creare un'istanza di un oggetto da questa classe. È necessario derivarne e implementare questo metodo
  2. = deletesignifica che il compilatore non genererà quei costruttori per te. AFAIK questo è consentito solo al costruttore di copie e all'operatore di assegnazione. Ma non sono troppo bravo con lo standard imminente.

4
Vi sono altri usi della =deletesintassi. Ad esempio, è possibile utilizzarlo per impedire esplicitamente un tipo di conversioni implicite che potrebbero aver luogo con la chiamata. Per questo basta cancellare le funzioni sovraccaricate. Dai un'occhiata alla pagina Wikipedia su C ++ 0x per maggiori informazioni.
LiKao,

Lo farò non appena ne troverò un po '. Indovina è tempo di recuperare il ritardo con c ++ 0X
mkaes

Sì, rocce C ++ 0x. Non vedo l'ora che GCC 4.5+ sia più comune, quindi posso iniziare a usare lambdas.
LiKao,

5
La descrizione per = deletenon è del tutto corretta. = deletepuò essere utilizzato per qualsiasi funzione, nel qual caso viene esplicitamente contrassegnato come eliminato e qualsiasi utilizzo provoca un errore del compilatore. Per le funzioni speciali dei membri, ciò significa in particolare che non vengono generate dal compilatore per te, ma questo è solo il risultato dell'eliminazione e non di ciò che = deleterealmente è.
MicroVirus,

28

Questo estratto da The C ++ Programming Language [4th Edition] - Il libro di Bjarne Stroustrup parla del vero scopo dietro l'utilizzo di =delete:

3.3.4 Soppressione delle operazioni

L'uso della copia o dello spostamento predefiniti per una classe in una gerarchia è in genere un disastro : dato solo un puntatore a una base, semplicemente non sappiamo quali membri ha la classe derivata, quindi non possiamo sapere come copiarli . Pertanto, la cosa migliore da fare è di solito eliminare le operazioni di copia e spostamento predefinite, ovvero eliminare le definizioni predefinite di queste due operazioni:

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();
    // ...
};

Ora un tentativo di copiare una forma verrà catturato dal compilatore.

Il =deletemeccanismo è generale, cioè può essere usato per sopprimere qualsiasi operazione



5

Gli standard di codifica con cui ho lavorato hanno avuto i seguenti per la maggior parte delle dichiarazioni di classe.

//  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)

Se si utilizza uno di questi 6, è sufficiente commentare la riga corrispondente.

Esempio: la classe FizzBus richiede solo dtor e quindi non usa l'altro 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)

Commentiamo solo 1 qui e ne installiamo l'implementazione altrove (probabilmente dove suggerisce lo standard di codifica). Gli altri 5 (di 6) non possono essere eliminati.

Puoi anche usare '= delete' per impedire promozioni implicite di valori di dimensioni diverse ... esempio

// 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

= deleteè una funzionalità introdotta in C ++ 11. In base a =deleteciò non sarà consentito chiamare quella funzione.

In dettaglio.

Supponiamo in una classe.

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

  }
};

Quando si chiama questa funzione per l'assegnazione obj non sarà consentita. L'operatore di assegnazione dei mezzi si limiterà a copiare da un oggetto a un altro.



1

Una funzione eliminata è implicitamente in linea

(Addendum alle risposte esistenti)

... E una funzione eliminata deve essere la prima dichiarazione della funzione (ad eccezione dell'eliminazione delle specializzazioni esplicite dei modelli di funzione - l'eliminazione dovrebbe avvenire alla prima dichiarazione della specializzazione), il che significa che non è possibile dichiarare una funzione e successivamente eliminarla, ad esempio, nella sua definizione locale a un'unità di traduzione.

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

Una funzione eliminata è implicitamente in linea. ( Nota: la regola di una definizione ( [basic.def.odr] ) si applica alle definizioni eliminate. - Nota finale ] Una definizione eliminata di una funzione deve essere la prima dichiarazione della funzione o, per una specializzazione esplicita di un modello di funzione , la prima dichiarazione di tale specializzazione. [Esempio:

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

- fine esempio )

Un modello di funzione principale con una definizione eliminata può essere specializzato

Sebbene una regola empirica generale sia quella di evitare la specializzazione dei modelli di funzioni in quanto le specializzazioni non partecipano al primo passo della risoluzione del sovraccarico, ci sono alcuni contesti in cui può essere utile. Ad esempio, quando si utilizza un modello di funzione principale non sovraccaricato senza definizione per abbinare tutti i tipi che non si vorrebbe convertire implicitamente in un sovraccarico di corrispondenza per conversione altrimenti; cioè, rimuovere implicitamente un numero di corrispondenze di conversione implicita implementando solo corrispondenze esatte di tipo nella specializzazione esplicita del modello di funzione principale non definito e non sovraccaricato.

Prima del concetto di funzione eliminata di C ++ 11, si poteva farlo semplicemente omettendo la definizione del modello di funzione principale, ma ciò dava errori di riferimento oscuri e indefiniti che probabilmente non davano alcun intento semantico all'autore del modello di funzione principale (omesso intenzionalmente ?). Se invece eliminiamo esplicitamente il modello di funzione principale, i messaggi di errore nel caso in cui non venga trovata una specializzazione esplicita adeguata diventano molto più piacevoli e mostrano anche che l'omissione / cancellazione della definizione del modello di funzione principale era intenzionale.

#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< ...
}

Tuttavia, invece di omettere semplicemente una definizione per il modello di funzione principale sopra, producendo un oscuro errore di riferimento indefinito quando nessuna specializzazione esplicita corrisponde, la definizione del modello primario può essere eliminata:

#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; */
}

Produrre un messaggio di errore più leggibile, in cui l'intento di eliminazione è anche chiaramente visibile (in cui un errore di riferimento indefinito potrebbe indurre lo sviluppatore a pensare che si tratti di un errore spiacevole).

Tornando al perché dovremmo mai voler usare questa tecnica? Anche in questo caso, specializzazioni esplicite potrebbero essere utili per implicitamente rimuovere le conversioni implicite.

#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

Questa è una novità negli standard C ++ 0x in cui è possibile eliminare una funzione ereditata.


11
È possibile eliminare qualsiasi funzione. Ad esempio, void foo(int); template <class T> void foo(T) = delete;interrompe tutte le conversioni implicite. Sono intaccettati solo argomenti di tipo, tutti gli altri tenteranno di creare un'istanza di una funzione "eliminata".
UncleBens,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.