Che cos’è un costruttore di conversione in C ++? Cosa serve?

Ho sentito dire che C ++ ha qualcosa chiamato “costruttore di conversioni” o “costruttore di conversione”. Cosa sono questi e cosa servono? L’ho visto menzionato per quanto riguarda questo codice:

class MyClass { public: int a, b; MyClass( int i ) {} } int main() { MyClass M = 1 ; } 

La definizione per un costruttore di conversione è diversa tra C ++ 03 e C ++ 11. In entrambi i casi deve essere un costruttore non explicit (altrimenti non sarebbe implicato nelle conversioni implicite), ma per C ++ 03 deve anche essere richiamabile con un singolo argomento. Questo è:

 struct foo { foo(int x); // 1 foo(char* s, int x = 0); // 2 foo(float f, int x); // 3 explicit foo(char x); // 4 }; 

I costruttori 1 e 2 sono entrambi costruttori di conversione in C ++ 03 e C ++ 11. Il costruttore 3, che deve accettare due argomenti, è solo un costruttore di conversioni in C ++ 11. L’ultimo, costruttore 4, non è un costruttore di conversione perché è explicit .

  • C ++ 03 : §12.3.1

    Un costruttore dichiarato senza l’ explicit funzione explicit che può essere chiamato con un singolo parametro specifica una conversione dal tipo del suo primo parametro al tipo della sua class. Tale costruttore è chiamato costruttore di conversione.

  • C ++ 11 : §12.3.1

    Un costruttore dichiarato senza specificatore della funzione explicit specifica una conversione dai tipi dei suoi parametri al tipo della sua class. Tale costruttore è chiamato costruttore di conversione.

Perché i costruttori con più di un singolo parametro sono considerati convertitori di costruttori in C ++ 11? Questo perché il nuovo standard ci fornisce una syntax utile per passare argomenti e restituire valori usando gli elenchi di parentesi . Considera il seguente esempio:

 foo bar(foo f) { return {1.0f, 5}; } 

La possibilità di specificare il valore restituito come elenco di controventi è considerata una conversione. Questo usa il costruttore di conversione per foo che accetta un float e un int . Inoltre, possiamo chiamare questa funzione facendo la bar({2.5f, 10}) . Questa è anche una conversione. Dal momento che sono conversioni, ha senso per i costruttori che usano convertire i costruttori .

È importante notare, quindi, che fare in modo che il costruttore di foo che accetta un float e un int abbiano l’identificatore di funzione explicit , impedirà la compilazione del codice precedente. La nuova syntax precedente può essere utilizzata solo se è disponibile un costruttore di conversione per eseguire il lavoro.

  • C ++ 11 : §6.6.3:

    Un’istruzione return con una lista-init rinforzata inizializza l’object o il riferimento da restituire dalla funzione mediante l’inizializzazione della lista di copia (8.5.4) dall’elenco di inizializzatore specificato.

    §8.5:

    L’inizializzazione che si verifica […] nell’argomento che passa […] è chiamata inizializzazione della copia.

    §12.3.1:

    Un costruttore esplicito costruisce oggetti proprio come costruttori non espliciti, ma lo fa solo dove la syntax di inizializzazione diretta (8.5) o dove cast (5.2.9, 5.4) sono esplicitamente usati.

Conversione implicita con il costruttore di conversione

Facciamo l’esempio nella domanda più complessa

 class MyClass { public: int a, b; MyClass( int i ) {} MyClass( const char* n, int k = 0 ) {} MyClass( MyClass& obj ) {} } 

I primi due costruttori stanno convertendo i costruttori. Il terzo è un costruttore di copie e, come tale, è un altro costruttore di conversioni.

Un costruttore di conversione abilita la conversione implicita dal tipo di argomento al tipo di costruttore. Qui, il primo costruttore abilita la conversione da un int a un object di class MyClass . Il secondo costruttore abilita la conversione da una stringa a un object di class MyClass . E terzo … da un object di class MyClass a un object di class MyClass !

Per essere un costruttore di conversioni, il costruttore deve avere un singolo argomento (nel secondo, il secondo argomento ha un valore predefinito) e deve essere dichiarato senza parole chiave explicit .

Quindi, l’inizializzazione in main può apparire come questa:

 int main() { MyClass M = 1 ; // which is an alternative to MyClass M = MyClass(1) ; MyClass M = "super" ; // which is an alternative to MyClass M = MyClass("super", 0) ; // or MyClass M = MyClass("super") ; } 

Parola chiave e costruttori espliciti

Ora, e se avessimo usato la parola chiave explicit ?

 class MyClass { public: int a, b; explicit MyClass( int i ) {} } 

Quindi, il compilatore non accetterebbe

  int main() { MyClass M = 1 ; } 

poiché questa è una conversione implicita. Invece, devi scrivere

  int main() { MyClass M(1) ; MyClass M = MyClass(1) ; MyClass* M = new MyClass(1) ; MyClass M = (MyClass)1; MyClass M = static_cast(1); } 

explicit parola chiave explicit deve sempre essere utilizzata per impedire la conversione implicita per un costruttore e si applica al costruttore in una dichiarazione di class.