Ho una domanda al riguardo:
class A
{
int a;
int* pa;
public:
A(int i):a(i) , pa(new int(a))
{
cout<<"A ctor"<<a<<endl;
}
~A()
{
delete pa;
cout<<"dtorn";
}
int * &get()
{
return pa;
}
};
class B : public A
{
int b;
public:
B (A obj): A(obj) , b(0)
{
cout<<"B ctorn";
}
~B()
{
cout<<"B dtorn";
}
};
int main()
{
int i = 23 ;
A* p = new B(i);
}
Puoi dirmi perché l'ultima riga in main
compila? Passo un int
in B
"s costruttore che si aspetta un A
oggetto invece. Credo che il int
è tradotto in un A
in B
Costruttore "s, ma perché?
Grazie in anticipo.
Avri.
risposte:
5 per risposta № 1Dal momento che non hai dichiarato A
costruttore come explicit
il compilatore sta creando un'istanza anomala di A
utilizzando i
e usandolo per inizializzare B
esempio. Se non vuoi che il compilatore esegua queste conversioni implicite, dichiara il tuo costruttore come explicit
. Quindi otterrai un errore del compilatore.
2 per risposta № 2
Perché A
ha un costruttore di parametri singolo che accetta un int
e non è contrassegnato explicit
puoi convertire implicitamente un int
ad A
.
Quando lo fai new B(i)
, perché l'unico costruttore valido per B
prende un A
, viene effettuato un tentativo di conversione i
ad A
e costruisci il nuovo B
da quello. Questa conversione viene eseguita creando un temporaneo A
usando il costruttore che accetta un int
.
Quando il B
l'oggetto è costruito, la classe base A
è una copia costruita dal temporaneo A
che significa copiare le variabili membro a
e pa
dal provvisorio A
.
Rigorosamente, perché il costruttore prende un A
oggetto per valore, il temporaneo è, concettualmente, copiato di nuovo. Il compilatore può, tuttavia, eliminare il temporaneo costruendo il parametro costruttore B
direttamente da i
quindi l'effetto potrebbe sembrare solo una singola copia.
Ciò causerà un errore grave perché quando il temporaneo A
è distrutto, delete pa
causerà l'allocazione dinamica int
da distruggere ma la classe base A del nuovo allocato B
l'oggetto avrà ancora una copia di questo puntatore che ora non punta più su un oggetto non valido. Se il compilatore non elimina una delle copie, un "doppio libero" avverrà immediatamente.
L'aspetto chiave di A
è che ha un distruttore definito dall'utente che esegue un'azione di risorsa (deallocazione). Questo è un forte avvertimento A
ha bisogno di un costruttore di copia definito dall'utente e di un operatore di assegnazione della copia poiché la versione generata dal compilatore probabilmente non funzionerà in modo coerente con il design di A
.
Questa è conosciuta come la "regola dei tre" che diceche se hai bisogno di una versione definita dall'utente di uno dei distruttori, costruttori di copie o operatori di assegnazione delle copie, probabilmente avrai bisogno di tutte le versioni definite dall'utente.
Dovresti tentare di liberare l'allocazione dinamica B
oggetto nel tuo esempio, probabilmente causerebbe un errore "doppio libero". Inoltre, A
Il distruttore di s dovrebbe essere contrassegnato come virtual
per una cancellazione attraverso un puntatore a A
per funzionare correttamente.
1 per risposta № 3
Poiché c'è una conversione da int
a A
, implicitamente il tuo codice è tradotto in
A* p = new B(A(i));