Aggiornamento C ++ 17
In C ++ 17, il significato di è A_factory_func()
cambiato dalla creazione di un oggetto temporaneo (C ++ <= 14) alla specifica dell'inizializzazione di qualsiasi oggetto a cui questa espressione è inizializzata (parlando in modo approssimativo) in C ++ 17. Questi oggetti (chiamati "oggetti risultato") sono le variabili create da una dichiarazione (like a1
), oggetti artificiali creati quando l'inizializzazione finisce per essere scartata o se è necessario un oggetto per l'associazione di riferimento (come, in A_factory_func();
. Nell'ultimo caso, un oggetto viene creato artificialmente, chiamato "materializzazione temporanea", perché A_factory_func()
non ha una variabile o un riferimento che altrimenti richiederebbe l'esistenza di un oggetto).
Come esempi nel nostro caso, nel caso di a1
e a2
regole speciali si dice che in tali dichiarazioni, l'oggetto risultato di un inizializzatore di valori dello stesso tipo a1
è variabile a1
e quindi A_factory_func()
inizializza direttamente l'oggetto a1
. Qualsiasi cast di stile funzionale intermedio non avrebbe alcun effetto, perché A_factory_func(another-prvalue)
semplicemente "attraversa" l'oggetto risultato del valore esterno per essere anche l'oggetto risultato del valore interno.
A a1 = A_factory_func();
A a2(A_factory_func());
Dipende dal tipo A_factory_func()
restituito. Suppongo che ritorni un A
- quindi sta facendo lo stesso - tranne che quando il costruttore di copie è esplicito, allora il primo fallirà. Leggi 8.6 / 14
double b1 = 0.5;
double b2(0.5);
Questo sta facendo lo stesso perché è un tipo incorporato (questo qui non significa un tipo di classe). Leggi 8.6 / 14 .
A c1;
A c2 = A();
A c3(A());
Questo non sta facendo lo stesso. Il primo predefinito inizializza if se non A
è un POD e non esegue alcuna inizializzazione per un POD (Leggi 8.6 / 9 ). La seconda copia inizializza: Value-inizializza un temporaneo e quindi copia quel valore in c2
(Leggi 5.2.3 / 2 e 8.6 / 14 ). Ciò ovviamente richiederà un costruttore di copie non esplicito (Leggi 8.6 / 14 e 12.3.1 / 3 e 13.3.1.3/1 ). Il terzo crea una dichiarazione di funzione per una funzione c3
che restituisce un A
e che accetta un puntatore a una funzione che restituisce un A
(Leggi 8.2 ).
Approfondimento sulle inizializzazioni Direct e Copia inizializzazione
Mentre sembrano identici e si suppone che facciano lo stesso, queste due forme sono notevolmente diverse in alcuni casi. Le due forme di inizializzazione sono l'inizializzazione diretta e copia:
T t(x);
T t = x;
C'è un comportamento che possiamo attribuire a ciascuno di essi:
- L'inizializzazione diretta si comporta come una chiamata di funzione a una funzione sovraccarica: le funzioni, in questo caso, sono i costruttori di
T
(inclusi explicit
quelli), e l'argomento è x
. La risoluzione del sovraccarico troverà il miglior costruttore corrispondente e, quando necessario, eseguirà qualsiasi conversione implicita richiesta.
- L'inizializzazione della copia crea una sequenza di conversione implicita: tenta di convertire
x
in un oggetto di tipo T
. (Quindi può copiare su quell'oggetto nell'oggetto inizializzato, quindi è necessario anche un costruttore di copie - ma questo non è importante di seguito)
Come vedi, l' inizializzazione della copia è in qualche modo una parte dell'inizializzazione diretta per quanto riguarda le possibili conversioni implicite: mentre l'inizializzazione diretta ha tutti i costruttori disponibili da chiamare, e inoltre può fare qualsiasi conversione implicita che deve abbinare i tipi di argomento, copia l'inizializzazione può semplicemente impostare una sequenza di conversione implicita.
Ho provato duramente e ho ottenuto il seguente codice per generare un testo diverso per ognuna di quelle forme , senza usare "ovvi" tramite i explicit
costruttori.
#include <iostream>
struct B;
struct A {
operator B();
};
struct B {
B() { }
B(A const&) { std::cout << "<direct> "; }
};
A::operator B() { std::cout << "<copy> "; return B(); }
int main() {
A a;
B b1(a); // 1)
B b2 = a; // 2)
}
// output: <direct> <copy>
Come funziona e perché genera questo risultato?
Inizializzazione diretta
Prima non sa nulla della conversione. Tenterà semplicemente di chiamare un costruttore. In questo caso, è disponibile il seguente costruttore ed è una corrispondenza esatta :
B(A const&)
Non c'è conversione, tanto meno una conversione definita dall'utente, necessaria per chiamare quel costruttore (si noti che qui non avviene nemmeno una conversione di qualifica const). E così l'inizializzazione diretta lo chiamerà.
Copia inizializzazione
Come detto sopra, l'inizializzazione della copia costruirà una sequenza di conversione quando a
non ha digitato B
o derivato da essa (che è chiaramente il caso qui). Quindi cercherà i modi per fare la conversione e troverà i seguenti candidati
B(A const&)
operator B(A&);
Nota come riscrivo la funzione di conversione: il tipo di parametro riflette il tipo di this
puntatore, che in una funzione membro non const è non-const. Ora, chiamiamo questi candidati x
come argomento. Il vincitore è la funzione di conversione: perché se abbiamo due funzioni candidate che accettano entrambe un riferimento allo stesso tipo, allora vince la versione meno const (questo è, tra l'altro, anche il meccanismo che preferisce la funzione membro non const richiede -const oggetti).
Nota che se cambiamo la funzione di conversione in una funzione membro const, allora la conversione è ambigua (perché entrambi hanno un tipo di parametro di A const&
allora): il compilatore Comeau la rifiuta correttamente, ma GCC la accetta in modalità non pedante. Il passaggio a -pedantic
rende anche l'emissione del giusto avviso di ambiguità.
Spero che questo aiuti in qualche modo a chiarire in che modo queste due forme differiscono!
A c1; A c2 = c1; A c3(c1);
.