Condividi tramite


Funzioni impostate come predefinite ed eliminate in modo esplicito

In C++11, le funzioni impostate come valore predefinito e rimosse offrono il controllo esplicito sulla generazione automatica delle funzioni membro speciali. Le funzioni deleted forniscono inoltre un semplice linguaggio per impedire il verificarsi di promozioni di tipo problematiche in argomenti di funzioni di tutti i tipi—funzioni membro di tipo speciale, nonché funzioni membro normali e non membro—che causerebbero una chiamata di funzione non desiderata.

Vantaggi delle funzioni impostate in modo esplicito come predefinite ed eliminate

In C++, il compilatore genera automaticamente il costruttore predefinito, il costruttore di copia, l'operatore di assegnazione di copia e il distruttore per un tipo se esso non viene dichiarato. Queste funzioni sono note come funzioni membro speciali e permettono ai tipi semplici definiti dall'utente in C++ di comportarsi come le strutture di C. Ovvero, è possibile crearli, copiarli e distruggerli senza ulteriori interventi sulla codifica. C++11 introduce la semantica di spostamento nel linguaggio e aggiunge il costruttore di spostamento e l'operatore di assegnazione di spostamento all'elenco delle funzioni membro speciali che il compilatore è in grado di generare automaticamente.

Ciò risulta utile per i tipi semplici, ma i tipi complessi spesso definiscono loro stessi una o più funzioni membro speciali e ciò può impedire la generazione automatica di altre funzioni membro speciali. In pratica:

  • Se un costruttore viene dichiarato in modo esplicito, non viene generato automaticamente alcun costruttore predefinito.

  • Se un distruttore virtuale viene dichiarato in modo esplicito, non viene generato automaticamente alcun distruttore predefinito.

  • Se un costruttore di spostamento o un operatore di assegnazione di spostamento viene dichiarato in modo esplicito:

    • Nessun costruttore di copia viene generato automaticamente.

    • Nessun operatore di assegnazione di copia viene generato automaticamente.

  • Se un costruttore di copia, un operatore di assegnazione di copia, un costruttore di spostamento, un operatore di assegnazione spostamento, o un distruttore è dichiarato in modo esplicito:

    • Nessun costruttore di spostamento viene generato automaticamente.

    • Nessun operatore di assegnazione di spostamento viene generato automaticamente.

Nota

Inoltre, lo standard C++11 specifica le seguenti regole aggiuntive:

  • Se un costruttore di copia o distruttore è dichiarato in modo esplicito, la generazione automatica dell'operatore di assegnazione di copia è deprecata.

  • Se un operatore di assegnamento di copia o distruttore è dichiarato in modo esplicito, la generazione automatica del costruttore di copia è deprecata.

In entrambi i casi, Visual Studio continua automaticamente a generare le funzioni necessarie in modo implicito, e non genera un avviso.

Le conseguenze di tali regole possono inoltre sfociare in gerarchie di oggetti. Ad esempio, se per qualsiasi motivo una classe base non ha un costruttore predefinito richiamabile da una classe di derivazione, ovvero un costruttore protected o public che non ha alcun parametro, allora una classe che deriva da essa non può generare automaticamente il proprio costruttore predefinito.

Queste regole possono complicare l'implementazione di tipi semplici definiti dall'utente e idiomi di C++ comuni, ad esempio rendere non copiabile un tipo definito dall'utente dichiarando l'operatore di costruttore di copia e quello di assegnazione di copia in privato senza definirli.

struct noncopyable
{
  noncopyable() {};
  
private:
  noncopyable(const noncopyable&);
  noncopyable& operator=(const noncopyable&);
};

Prima di C++11 il frammento di codice era la forma idiomatica dei tipi non copiabili. Tuttavia, presenta alcuni problemi:

  • Il costruttore di copia deve essere dichiarato in modo privato per venire nascosto, ma poiché è dichiarato, viene impedita la generazione automatica del costruttore predefinito. Se si intende disporre di un costruttore predefinito è necessario definirlo in modo esplicito, anche se non esegue alcuna operazione.

  • Anche se il costruttore predefinito definito in modo esplicito non esegue alcuna operazione, viene considerato non comune dal compilatore. È meno efficiente di un costruttore predefinito generato automaticamente e impedisce a noncopyable di essere effettivamente un tipo POD.

  • Anche se il costruttore di copia e l'operatore di assegnazione di copia sono invisibili al codice esterno, le funzioni membro e gli elementi friend di noncopyable sono comunque in grado di visualizzarli e chiamarli. Se vengono dichiarati ma non definiti, chiamandoli viene generato un errore del linker.

  • Sebbene si tratti di un linguaggio in genere accettato, lo scopo non è chiaro a meno che non si conoscano tutte le regole per la generazione automatica delle funzioni membro speciali.

In C++11, il linguaggio non-copyable può essere implementato in modo più semplice.

struct noncopyable
{
  noncopyable() =default;
  noncopyable(const noncopyable&) =delete;
  noncopyable& operator=(const noncopyable&) =delete;
};

Si noti come vengono risolti i problemi relativi al linguaggio precedente a C++11:

  • La generazione del costruttore predefinito viene impedita dichiarando il costruttore di copia, ma è possibile ripristinarlo impostandolo in modo esplicito come predefinito.

  • Le funzioni membro speciali impostate in modo esplicito come predefinite verranno considerate irrilevanti, pertanto non esiste una riduzione delle prestazioni, inoltre a noncopyable non viene impedito di essere un tipo POD true.

  • Il costruttore di copia e l'operatore di assegnazione di copia sono pubblici ma vengono eliminati. È un errore in fase di compilazione per definire oppure chiamare una funzione eliminata.

  • Lo scopo è chiaro a chiunque comprenda =default e =delete. Non è necessario comprendere le regole per la generazione automatica di funzioni membro speciali.

Simili idiomi servono a creare tipi definiti dall'utente che non sono per dispositivi mobili, che possono essere allocati solo in modo dinamico, o che non possono essere allocati in modo dinamico. Ognuno di questi idiomi dispone di implementazioni precedenti a C++11 soggette a problemi simili e che vengono risolte in modo analogo in C++11 tramite la relativa implementazione in termini di funzioni membro speciali impostate come predefinite ed eliminate.

Funzioni impostata in modo esplicito come predefinite

È possibile impostare sul valore predefinito qualsiasi delle funzioni membro speciali, per stabilire esplicitamente che la funzione membro speciale utilizza l'implementazione predefinita, per definire una funzione membro speciale con un qualificatore di accesso non pubblico o per ripristinare una funzione membro speciale la cui generazione automatica è stata impedita da altre circostanze.

Una funzione membro speciale viene impostata come predefinita dichiarandola come nell'esempio seguente:

struct widget
{
  widget()=default;

  inline widget& operator=(const widget&);
};

inline widget& widget::operator=(const widget&) =default;

Si noti che è possibile impostare come valore predefinito una funzione membro speciale al di fuori del corpo di una classe a condizione che sia inlinable.

A causa dei vantaggi in termini di prestazioni delle funzioni membro speciali irrilevanti, è consigliabile preferire le funzioni membro speciali generate automaticamente rispetto ai corpi di funzioni vuote quando si desidera il comportamento predefinito. È possibile eseguire questa operazione in modo esplicito impostare il valore predefinito della funzione membro speciale o non dichiarandola (e non dichiarando neanche altre funzioni membro speciali che ne impedirebbero la generazione automatica).

Nota

Visual Studio non supporta i costruttori di spostamento o gli operatori di assegnazione di spostamento impostati come valore predefinito, come dettato dallo standard C++11.Per ulteriori informazioni, vedere la sezione di Supporto delle funzionalità C++11 (C++ moderno) relativa alle funzioni impostate come predefinite ed eliminate.

Funzioni eliminate

È possibile eliminare le funzioni membro speciali oltre alle funzioni membro normali e alle funzioni non membro per evitarne la definizione o la chiamata. L'eliminazione delle funzioni membro speciali fornisce un modo più semplice di impedire al compilatore di generare funzioni membro speciali che non si desiderano. La funzione deve essere eliminata come viene dichiarata; non può essere eliminata in seguito nel modo in cui una funzione può essere dichiarata e quindi essere successivamente impostata come valore predefinito.

struct widget
{
  // deleted operator new prevents widget from being dynamically allocated.
  void* operator new(std::size_t) =delete;
};

L'eliminazione di una normale funzione membro o di funzioni non membro impedisce alle promozioni di tipo problematico di causare la chiamata di una funzione indesiderata. Questa operazione è possibile perché le funzioni eliminate partecipano ancora alla risoluzione dell'overload e offrono una migliore corrispondenza rispetto alla funzione che può essere chiamata dopo la promozione dei tipi. La chiamata di funzione viene risolta nella funzione più specifica—ma eliminata— e genera un errore del compilatore.

// deleted overload prevents call through type promotion of float to double from succeeding.
void call_with_true_double_only(float) =delete;
void call_with_true_double_only(double param) { return; }

Si noti nell'esempio precedente che la chiamata a call_with_true_double_only utilizzando un argomento float provocherebbe un errore del compilatore, ma la chiamata a call_with_true_double_only utilizzando un argomento int non lo provocherebbe; nel caso int, l'argomento verrà promosso da int a double e richiama correttamente la versione double della funzione, anche se ciò potrebbe non essere il comportamento voluto. Per garantire che le chiamate a questa funzione utilizzando un argomento non double causino un errore del compilatore, è possibile dichiarare una versione di template della funzione eliminata.

template < typename T >
void call_with_true_double_only(T) =delete; //prevent call through type promotion of any T to double from succeeding.

void call_with_true_double_only(double param) { return; } // also define for const double, double&, etc. as needed.