Costruttori (C++)
Un costruttore è una funzione membro speciale che inizializza un'istanza della relativa classe. Per chiamare un costruttore, si utilizza il nome della classe con i parametri racchiusi tra parentesi tonde o graffe.
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
class SomeClass{
public:
static void set_box(const Box& aBox) {}
};
int main(){
Box box1(1, 2, 3);
Box box2{ 2, 3, 4 };
SomeClass::set_box(Box{ 5, 6, 7 });
}
Per altre informazioni, vedere Regole per la dichiarazione di costruttori. Per altre informazioni sull'inizializzazione, vedere Inizializzazione.
Ordine di costruzione
Di seguito è indicato l'ordine in base al quale un costruttore esegue le attività.
Chiama i costruttori delle classi base e dei membri nell'ordine di dichiarazione.
Se la classe è derivata dalle classi base virtuali, inizializza i puntatori di base virtuali dell'oggetto.
Se la classe dispone di funzioni virtuali o le eredita, inizializza i puntatori a funzioni virtuali dell'oggetto. I puntatori a funzioni virtuali puntano alla tabella delle funzioni virtuali della classe per consentire la corretta associazione delle chiamate di funzioni virtuali al codice.
Esegue il codice nel corpo della funzione.
L'esempio seguente illustra l'ordine in cui vengono chiamati i costruttori di classi base e di membri in un costruttore per una classe derivata. Prima di tutto, viene chiamato il costruttore base, quindi i membri della classe base vengono inizializzati nell'ordine in cui vengono visualizzati nella dichiarazione di classe e infine viene chiamato il costruttore derivato.
#include <iostream>
using namespace std;
class Contained1 {
public:
Contained1() {
cout << "Contained1 constructor." << endl;
}
};
class Contained2 {
public:
Contained2() {
cout << "Contained2 constructor." << endl;
}
};
class Contained3 {
public:
Contained3() {
cout << "Contained3 constructor." << endl;
}
};
class BaseContainer {
public:
BaseContainer() {
cout << "BaseContainer constructor." << endl;
}
private:
Contained1 c1;
Contained2 c2;
};
class DerivedContainer : public BaseContainer {
public:
DerivedContainer() : BaseContainer() {
cout << "DerivedContainer constructor." << endl;
}
private:
Contained3 c3;
};
int main() {
DerivedContainer dc;
int x = 3;
}
Di seguito viene riportato l'output:
Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.
Se un costruttore genera un'eccezione, l'ordine di eliminazione permanente è l'inverso dell'ordine di costruzione:
Il codice nel corpo della funzione del costruttore viene rimosso.
Gli oggetti classe base e membro vengono eliminati in modo permanente nell'ordine inverso rispetto alla dichiarazione.
Se il costruttore non è delegante, tutti i membri e gli oggetti di classe base completamente costruiti vengono eliminati in modo permanente. Tuttavia, poiché l'oggetto stesso non viene costruito completamente, il distruttore non viene eseguito.
Costruttori espliciti
L'utilizzo della parola chiave explicit in un costruttore può impedire le conversioni implicite di tipi se il costruttore dispone di un solo parametro o se tutti i parametri tranne uno presentano un valore predefinito. Per altre informazioni, vedere Costruttori (C++).
Costruttori predefiniti
I costruttori predefiniti, ovvero i costruttori che non dispongono di parametri, seguono regole leggermente diverse.
Se in una classe non viene dichiarato alcun costruttore, il compilatore fornisce un costruttore predefinito:
class Box {
int m_width;
int m_length;
int m_height;
};
int main(){
Box box1{};
Box box2;
}
Quando si chiama un costruttore predefinito e si tenta di utilizzare le parentesi, viene generato un avviso:
class myclass{};
int main(){
myclass mc(); // warning C4930: prototyped function not called (was a variable definition intended?)
}
Di seguito è riportato un esempio del problema 'Most Vexing Parse'. Poiché l'espressione di esempio può essere interpretata come dichiarazione di una funzione o come chiamata di un costruttore predefinito e dal momento che i parser C++ favoriscono le dichiarazioni rispetto ad altri elementi, tale espressione viene considerata come una dichiarazione di funzione. Per altre informazioni, vedere la pagina Most Vexing Parse.
Se vengono dichiarati costruttori non predefiniti, il compilatore non fornisce un costruttore predefinito:
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
int main(){
Box box1(1, 2, 3);
Box box2{ 2, 3, 4 };
Box box4; // compiler error C2512: no appropriate default constructor available
}
Se la classe non dispone di un costruttore predefinito, non è possibile costruire una matrice di oggetti di tale classe usando solo la sintassi tra parentesi quadre. Ad esempio, nel caso del blocco di codice precedente, non è possibile dichiarare una matrice di Boxes nel modo seguente:
Box boxes[3]; // compiler error C2512: no appropriate default constructor available
È tuttavia possibile usare un set di elenchi di inizializzatori per inizializzare una matrice di caselle:
Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Copiare e spostare i costruttori
Un costruttore di copia utilizza un riferimento a un oggetto per eseguirne una copia. Se non si fornisce un costruttore di copia per una classe, il compilatore ne crea uno predefinito, anche quando sono stati dichiarati un'operazione di copia, un'operazione di spostamento o un distruttore. Per altre informazioni, vedere Regole per la dichiarazione di costruttori.
Un costruttore di spostamento consente di trasferire tutta la memoria allocata da un oggetto a un altro. Per altre informazioni, vedere Procedura: scrivere un costruttore di spostamento.
Costruttori eliminati e impostati in modo esplicito come predefiniti
È possibile impostare in modo esplicito come predefiniti i costruttori di copia, i costruttori predefiniti, gli operatori di assegnazione di copia e i distruttori, ma l'impostazione in modo esplicito dei costruttori di spostamento e degli operatori di assegnazione di spostamento come predefiniti non è supportata. (Questo supporto è presente in Visual Studio 2015). È possibile eliminare in modo esplicito tutte le funzioni speciali. Per altre informazioni, vedere Funzioni impostate come predefinite ed eliminate in modo esplicito.
Costruttori nelle classi derivate
Un costruttore di classi derivate chiama sempre un costruttore di classi base, in modo da poter usare le classi base completamente costruite prima che vengano completate eventuali operazioni aggiuntive. I costruttori di classi base vengono chiamati in ordine di derivazione, ad esempio se ClassA è derivata da ClassB, che è a sua volta derivata da ClassC, viene chiamato per primo il costruttore di ClassC, quindi il costruttore di ClassB e infine il costruttore di ClassA.
Se una classe base non dispone di un costruttore predefinito, è necessario fornire i parametri del costruttore della classe base nel costruttore della classe derivata:
class Box {
public:
Box(int width, int length, int height){
m_width = width;
m_length = length;
m_height = height;
}
private:
int m_width;
int m_length;
int m_height;
};
class StorageBox : public Box {
public:
StorageBox(int width, int length, int height, const string label&) : Box(width, length, height){
m_label = label;
}
private:
string m_label;
};
int main(){
const string aLabel = "aLabel";
StorageBox sb(1, 2, 3, aLabel);
}
Costruttori di classi con ereditarietà multipla
Se una classe è derivata da più classi base, i costruttori della classe base vengono richiamati nell'ordine in cui sono elencati nella dichiarazione della classe derivata:
#include <iostream>
using namespace std;
class BaseClass1 {
public:
BaseClass1() {
cout << "BaseClass1 constructor." << endl;
}
};
class BaseClass2 {
public:
BaseClass2() {
cout << "BaseClass2 constructor." << endl;
}
};
class BaseClass3{
public:
BaseClass3() {
cout << "BaseClass3 constructor." << endl;
}
};
class DerivedClass : public BaseClass1, public BaseClass2, public BaseClass3 {
public:
DerivedClass() {
cout << "DerivedClass constructor." << endl;
}
};
int main() {
DerivedClass dc;
}
È necessario prevedere il seguente output:
BaseClass1 constructor.
BaseClass2 constructor.
BaseClass3 constructor.
DerivedClass constructor.
Funzioni virtuali nei costruttori
È consigliabile prestare attenzione quando si chiamano le funzioni virtuali nei costruttori. Poiché il costruttore della classe base viene sempre richiamato prima del costruttore della classe derivata, la funzione chiamata nel costruttore base è la versione della classe base, non quella della classe derivata. Nell'esempio seguente, la costruzione di una classe DerivedClass fa in modo che l'implementazione di BaseClass da parte della classe print_it() venga eseguita prima che il costruttore della classe DerivedClass determini l'esecuzione dell'implementazione di DerivedClass da parte della classe print_it():
#include <iostream>
using namespace std;
class BaseClass{
public:
BaseClass(){
print_it();
}
virtual void print_it() {
cout << "BaseClass print_it" << endl;
}
};
class DerivedClass : public BaseClass {
public:
DerivedClass() {
print_it();
}
virtual void print_it(){
cout << "Derived Class print_it" << endl;
}
};
int main() {
DerivedClass dc;
}
Di seguito viene riportato l'output:
BaseClass print_it
Derived Class print_it
Costruttori e classi composite
Le classi contenenti membri di tipo classe sono note come classi composite. Quando viene creato un membro di tipo classe di una classe composita, il costruttore viene chiamato prima del costruttore della classe. Quando una classe contenuta è priva di un costruttore predefinito, è necessario usare un elenco di inizializzazione nel costruttore della classe composita. Nell'esempio precedente relativo a StorageBox, se si modifica il tipo della variabile membro m_label in una nuova classe Label, è necessario chiamare il costruttore della classe base e inizializzare la variabile m_label nel costruttore StorageBox:
class Label {
public:
Label(const string& name, const string& address) { m_name = name; m_address = address; }
string m_name;
string m_address;
};
class StorageBox : public Box {
public:
StorageBox(int width, int length, int height, Label label)
: Box(width, length, height), m_label(label){}
private:
Label m_label;
};
int main(){
// passing a named Label
Label label1{ "some_name", "some_address" };
StorageBox sb1(1, 2, 3, label1);
// passing a temporary label
StorageBox sb2(3, 4, 5, Label{ "another name", "another address" });
// passing a temporary label as an initializer list
StorageBox sb3(1, 2, 3, {"myname", "myaddress"});
}
Delega dei costruttori
Un costruttore delegante chiama un altro costruttore nella stessa classe per eseguire parte delle operazioni di inizializzazione. Nell'esempio seguente, la classe derivata dispone di tre costruttori: il secondo costruttore delega al primo e il terzo costruttore delega al secondo:
#include <iostream>
using namespace std;
class ConstructorDestructor {
public:
ConstructorDestructor() {
cout << "ConstructorDestructor default constructor." << endl;
}
ConstructorDestructor(int int1) {
cout << "ConstructorDestructor constructor with 1 int." << endl;
}
ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
cout << "ConstructorDestructor constructor with 2 ints." << endl;
throw exception();
}
ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
cout << "ConstructorDestructor constructor with 3 ints." << endl;
}
~ConstructorDestructor() {
cout << "ConstructorDestructor destructor." << endl;
}
};
int main() {
ConstructorDestructor dc(1, 2, 3);
}
Di seguito viene riportato l'output:
ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor constructor with 3 ints.
L'oggetto creato dai costruttori viene completamente inizializzato non appena viene completato un costruttore. DerivedContainer(int int1) ha esito positivo, ma DerivedContainer(int int1, int int2) ha esito negativo e viene chiamato il distruttore.
class ConstructorDestructor {
public:
ConstructorDestructor() {
cout << "ConstructorDestructor default constructor." << endl;
}
ConstructorDestructor(int int1) {
cout << "ConstructorDestructor constructor with 1 int." << endl;
}
ConstructorDestructor(int int1, int int2) : ConstructorDestructor(int1) {
cout << "ConstructorDestructor constructor with 2 ints." << endl;
throw exception();
}
ConstructorDestructor(int int1, int int2, int int3) : ConstructorDestructor(int1, int2) {
cout << "ConstructorDestructor constructor with 3 ints." << endl;
}
~ConstructorDestructor() {
cout << "ConstructorDestructor destructor." << endl;
}
};
int main() {
try {
ConstructorDestructor cd{ 1, 2, 3 };
}
catch (const exception& ex){
}
}
Output:
ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor destructor.
Per altre informazioni, vedere Inizializzazione uniforme e costruttori deleganti.