Partager via


Constructeurs (C++)

Un constructeur est une fonction membre spéciale qui initialise une instance de sa classe. Pour appeler un constructeur, vous utilisez le nom de classe avec des paramètres placés entre accolades ou parenthèses.

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 });
}

Pour plus d'informations, consultez Règles de déclaration des constructeurs. Pour plus d'informations sur l'initialisation, consultez Initialisation.

Ordre de construction

Un constructeur effectue son travail dans l'ordre suivant :

  1. Il appelle les constructeurs membres et de classe de base dans l'ordre de déclaration.

  2. Si la classe est dérivée de classes de base virtuelles, il initialise les pointeurs de base virtuels de l'objet.

  3. Si la classe possède ou hérite des fonctions virtuelles, il initialise les pointeurs de fonction virtuelle de l'objet. Les pointeurs de fonction virtuelle pointent sur la table de fonctions virtuelles de la classe pour permettre la liaison correcte des appels de fonction virtuelle au code.

  4. Il exécute le code dans son corps de fonction.

L'exemple suivant montre l'ordre dans lequel les constructeurs de classe de base et membres sont appelés dans le constructeur pour une classe dérivée. D'abord, le constructeur de base est appelé, puis les membres de classe de base sont initialisés dans l'ordre dans lequel ils apparaissent dans la déclaration de classe, puis le constructeur dérivé est appelé.

#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;
}

Voici la sortie :

Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.

Si un constructeur lève une exception, l'ordre de destruction est l'inverse de l'ordre de construction :

  1. Le code dans le corps de la fonction constructeur est déroulé.

  2. Les objets de classe de base et membres sont détruits dans l'ordre inverse de déclaration.

  3. Si le constructeur ne délègue pas, tous les membres et objets de classe de base entièrement construits sont détruits. Toutefois, l'objet lui-même n'étant pas complètement construit, le destructeur n'est pas exécuté.

Constructeurs explicites

L'utilisation du mot clé explicit sur un constructeur peut empêcher les conversions de types implicites si le constructeur a un seul paramètre ou si tous les paramètres sauf un ont une valeur par défaut. Pour plus d'informations, consultez Constructeurs (C++).

Constructeurs par défaut

Les constructeurs par défaut(autrement dit, ceux qui n'ont pas de paramètre) respectent des règles légèrement différentes.

Si aucun constructeur n'est déclaré dans une classe, le compilateur fournit un constructeur par défaut :

class Box {
    int m_width;
    int m_length;
    int m_height;
};

int main(){

    Box box1{};
    Box box2;
}

Lorsque vous appelez un constructeur par défaut et que vous essayez d'utiliser des parenthèses, un avertissement s'affiche :

class myclass{};
int main(){
myclass mc();     // warning C4930: prototyped function not called (was a variable definition intended?)
}

Voici un exemple de problème de type « Most Vexing Parse ». Puisque l'exemple d'expression peut être interprété comme la déclaration d'une fonction ou comme l'appel d'un constructeur par défaut, et puisque les analyseurs C++ privilégient les déclarations sur d'autres choses, l'expression est traitée comme une déclaration de fonction. Pour plus d’informations, consultez Most Vexing Parse.

Si des constructeurs autres que ceux par défaut sont déclarés, le compilateur ne fournit pas de constructeur par défaut :

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
}

Si une classe n'a aucun constructeur par défaut, un tableau d'objets de cette classe ne peut pas être construit seulement à l'aide de la syntaxe avec crochets. Par exemple, dans le bloc de code précédent, un tableau de Boxes ne peut pas être déclaré comme suit :

Box boxes[3];    // compiler error C2512: no appropriate default constructor available

Toutefois, vous pouvez utiliser un ensemble de listes d'initialiseurs pour initialiser un tableau de Boxes :

Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };

Constructeurs de copie et de déplacement

Un constructeur de copie utilise une référence à un objet pour en effectuer une copie. Si vous ne fournissez pas de constructeur de copie pour une classe, le compilateur en crée un par défaut, même lorsqu'une opération de copie, une opération de déplacement ou un destructeur a été déclaré. Pour plus d'informations, consultez Règles de déclaration des constructeurs.

Un constructeur de déplacement autorise le transfert de la mémoire allouée d'un objet vers un autre. Pour plus d'informations, consultez Comment : écrire un constructeur move.

Constructeurs explicitement utilisés par défaut et supprimés

Vous pouvez utiliser de manière explicite des constructeurs de copie par défaut, des constructeurs par défaut, des opérateurs d’assignation de copie et des destructeurs, mais l’utilisation explicite par défaut de constructeurs de déplacement et des opérateurs d’assignation de déplacement n’est pas prise en charge. (Cette prise en charge existe dans Visual Studio 2015.) Vous pouvez supprimer de manière explicite toutes les fonctions spéciales. Pour plus d'informations, consultez Fonctions utilisées par défaut et supprimées explicitement.

Constructeurs dans les classes dérivées

Un constructeur de classe dérivée appelle toujours un constructeur de classe de base, afin de pouvoir s'appuyer sur des classes de base complètement construites avant d'effectuer tout travail supplémentaire. Les constructeurs de classe de base sont appelés par ordre de dérivation. Par exemple, si ClasseA est dérivée de ClasseB, qui est dérivée de ClasseC, le constructeur ClasseC est appelé en premier, puis le constructeur ClasseB, puis le constructeur ClasseA.

Si une classe de base n'a pas de constructeur par défaut, vous devez fournir les paramètres du constructeur de classe de base dans le constructeur de classe dérivée :

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);
} 

Constructeurs pour les classes ayant un héritage multiple

Si une classe est dérivée de plusieurs classes de base, les constructeurs de classe de base sont appelés dans l'ordre dans lequel ils sont répertoriés dans la déclaration de la classe dérivée :

#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;
}

Vous devez obtenir la sortie suivante :

BaseClass1 constructor.
BaseClass2 constructor.
BaseClass3 constructor.
DerivedClass constructor.

Fonctions virtuelles dans les constructeurs

Nous vous recommandons d'être prudent lorsque vous appelez des fonctions virtuelles dans des constructeurs. Le constructeur de classe de base étant toujours appelé avant le constructeur de classe dérivée, la fonction qui est appelée dans le constructeur de base est la version de classe de base, et non la version de classe dérivée. Dans l'exemple suivant, la construction d'un objet DerivedClass provoque l'exécution de l'implémentation BaseClass de print_it() avant que le constructeur DerivedClass provoque l'exécution de l'implémentation DerivedClass de 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;
}

Voici la sortie :

BaseClass print_it
Derived Class print_it

Constructeurs et classes composites

Les classes qui contiennent des membres de type classe portent le nom de classes composites. Lorsqu'un membre de type classe d'une classe composite est créé, le constructeur est appelé avant le propre constructeur de la classe. Lorsqu'une classe contenue n'a pas de constructeur par défaut, vous devez utiliser une liste d'initialisation dans le constructeur de la classe composite. Dans l'exemple StorageBox précédent, si vous remplacez le type de la variable membre m_label par une nouvelle classe Label, vous devez appeler le constructeur de classe de base et initialiser la variable m_label dans le constructeur 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"});
}

Constructeurs qui effectuent une délégation

Un constructeur qui effectue une délégation appelle un constructeur différent dans la même classe pour effectuer une partie du travail d'initialisation. Dans l'exemple suivant, la classe dérivée a trois constructeurs : le second constructeur délègue au premier et le troisième constructeur délègue au second :

#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);
}

Voici la sortie :

ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor constructor with 3 ints.

L'objet créé par les constructeurs est entièrement initialisé dès qu'un constructeur est terminé. DerivedContainer(int int1) réussit, mais DerivedContainer(int int1, int int2) échoue et le destructeur est appelé.

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){
    }
}

Sortie :

ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor destructor.

Pour plus d'informations, consultez Constructeurs d'initialisation uniforme et de délégation.

Voir aussi

Référence

Fonctions membres spéciales (C++)