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
funzioneexplicit
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.