Constructeurs (C++)

Pour personnaliser la façon dont une classe initialise ses membres ou pour appeler des fonctions lorsqu’un objet de votre classe est créé, définissez un constructeur. Un constructeur porte le même nom que la classe et n'a aucune valeur de retour. Vous pouvez définir autant de constructeurs surchargés que nécessaire pour personnaliser l’initialisation de différentes façons. En règle générale, les constructeurs disposent d’une accessibilité publique afin que le code en dehors de la définition de classe ou de la hiérarchie d’héritage puisse créer des objets de la classe. Mais vous pouvez également déclarer un constructeur en tant que protected ou private.

Les constructeurs peuvent éventuellement prendre une liste d’initialiseurs de membres. Il s’agit d’un moyen plus efficace d’initialiser les membres de classe que d’affecter des valeurs dans le corps du constructeur. L’exemple suivant montre une classe Box avec trois constructeurs surchargés. Les deux dernières listes d’init de membre utilisent :

class Box {
public:
    // Default constructor
    Box() {}

    // Initialize a Box with equal dimensions (i.e. a cube)
    explicit Box(int i) : m_width(i), m_length(i), m_height(i) // member init list
    {}

    // Initialize a Box with custom dimensions
    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}

    int Volume() { return m_width * m_length * m_height; }

private:
    // Will have value of 0 when default constructor is called.
    // If we didn't zero-init here, default constructor would
    // leave them uninitialized with garbage values.
    int m_width{ 0 };
    int m_length{ 0 };
    int m_height{ 0 };
};

Lorsque vous déclarez une instance d’une classe, le compilateur choisit le constructeur à appeler en fonction des règles de résolution de surcharge :

int main()
{
    Box b; // Calls Box()

    // Using uniform initialization (preferred):
    Box b2 {5}; // Calls Box(int)
    Box b3 {5, 8, 12}; // Calls Box(int, int, int)

    // Using function-style notation:
    Box b4(2, 4, 6); // Calls Box(int, int, int)
}
  • Les constructeurs peuvent être déclarés en tant que inline, , explicitfriendou constexpr.
  • Un constructeur peut initialiser un objet qui a été déclaré en tant que const, volatile ou const volatile. L’objet devient const une fois le constructeur terminé.
  • Pour définir un constructeur dans un fichier d’implémentation, donnez-lui un nom qualifié comme n’importe quelle autre fonction membre : Box::Box(){...}.

Listes d’initialiseurs de membres

Un constructeur peut éventuellement avoir une liste d’initialiseurs de membre, qui initialise les membres de classe avant l’exécution du corps du constructeur. (Une liste d’initialiseurs de membre n’est pas la même chose qu’une liste d’initialiseurs de type std::initializer_list<T>.)

Préférer les listes d’initialiseurs de membres à l’affectation de valeurs dans le corps du constructeur. Une liste d’initialiseurs de membres initialise directement les membres. L’exemple suivant montre la liste d’initialiseurs de membres, qui se compose de toutes les expressions après le identifier(argument) signe deux-points :

    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}

L’identificateur doit faire référence à un membre de classe ; elle est initialisée avec la valeur de l’argument. L’argument peut être l’un des paramètres du constructeur, un appel de fonction ou un std::initializer_list<T>.

const les membres et les membres du type référence doivent être initialisés dans la liste d’initialiseurs de membres.

Pour garantir que les classes de base sont entièrement initialisées avant l’exécution du constructeur dérivé, appelez tous les constructeurs de classe de base paramétrables dans la liste d’initialiseurs.

Constructeurs par défaut

Les constructeurs par défaut n’ont généralement aucun paramètre, mais ils peuvent avoir des paramètres avec des valeurs par défaut.

class Box {
public:
    Box() { /*perform any required default initialization steps*/}

    // All params have default values
    Box (int w = 1, int l = 1, int h = 1): m_width(w), m_height(h), m_length(l){}
...
}

Les constructeurs par défaut sont l’une des fonctions membres spéciales. Si aucun constructeur n’est déclaré dans une classe, le compilateur fournit un constructeur par défaut implicite inline .

#include <iostream>
using namespace std;

class Box {
public:
    int Volume() {return m_width * m_height * m_length;}
private:
    int m_width { 0 };
    int m_height { 0 };
    int m_length { 0 };
};

int main() {
    Box box1; // Invoke compiler-generated constructor
    cout << "box1.Volume: " << box1.Volume() << endl; // Outputs 0
}

Si vous vous appuyez sur un constructeur par défaut implicite, veillez à initialiser les membres dans la définition de classe, comme indiqué dans l’exemple précédent. Sans ces initialiseurs, les membres sont non initialisés et l’appel Volume() produit une valeur de garbage. En règle générale, il est recommandé d’initialiser les membres de cette façon, même en ne s’appuyant pas sur un constructeur par défaut implicite.

Vous pouvez empêcher le compilateur de générer un constructeur par défaut implicite en le définissant comme supprimé :

    // Default constructor
    Box() = delete;

Un constructeur par défaut généré par le compilateur est défini comme supprimé si des membres de classe ne sont pas constructibles par défaut. Par exemple, tous les membres du type de classe et leurs membres de type classe doivent avoir un constructeur et des destructeurs par défaut accessibles. Tous les membres de données du type référence et tous les const membres doivent avoir un initialiseur de membre par défaut.

Lorsque vous appelez un constructeur par défaut généré par le compilateur et que vous essayez d’utiliser des parenthèses, un avertissement est émis :

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

Cette instruction est un exemple du problème « Most Vexing Parse ». Vous pouvez interpréter myclass md(); soit comme une déclaration de fonction, soit comme l’appel d’un constructeur par défaut. Étant donné que les analyseurs C++ favorisent les déclarations par rapport à d’autres choses, l’expression est traitée comme une déclaration de fonction. Pour plus d’informations, consultez La plupart des analyseurs Vexing.

Si des constructeurs autres que les constructeurs 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 box3; // C2512: no appropriate default constructor available
}

Si une classe n’a pas de constructeur par défaut, un tableau d’objets de cette classe ne peut pas être construit à l’aide de la syntaxe entre crochets seul. Par exemple, étant donné le bloc de code précédent, un tableau de cases ne peut pas être déclaré comme suit :

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

Toutefois, vous pouvez utiliser un ensemble de listes d’initialiseurs pour initialiser un tableau d’objets Box :

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

Pour plus d’informations, consultez Initialiseurs.

Constructeurs de copie

Un constructeur de copie initialise un objet en copiant les valeurs membres à partir d’un objet du même type. Si vos membres de classe sont tous des types simples tels que des valeurs scalaires, le constructeur de copie généré par le compilateur est suffisant et vous n’avez pas besoin de définir votre propre type. Si votre classe nécessite une initialisation plus complexe, vous devez implémenter un constructeur de copie personnalisé. Par exemple, si un membre de classe est un pointeur, vous devez définir un constructeur de copie pour allouer une nouvelle mémoire et copier les valeurs de l’objet pointu de l’autre. Le constructeur de copie généré par le compilateur copie simplement le pointeur, de sorte que le nouveau pointeur pointe toujours vers l’emplacement de mémoire de l’autre.

Un constructeur de copie peut avoir l’une des signatures suivantes :

    Box(Box& other); // Avoid if possible--allows modification of other.
    Box(const Box& other);
    Box(volatile Box& other);
    Box(volatile const Box& other);

    // Additional parameters OK if they have default values
    Box(Box& other, int i = 42, string label = "Box");

Lorsque vous définissez un constructeur de copie, vous devez également définir un opérateur d’assignation de copie (=). Pour plus d’informations, consultez Les constructeurs d’affectation et de copie et les opérateurs d’assignation de copie.

Vous pouvez empêcher la copie de votre objet en définissant le constructeur de copie comme supprimé :

    Box (const Box& other) = delete;

Toute tentative de copie de l’objet génère l’erreur C2280 : tentative de référence d’une fonction supprimée.

Déplacer des constructeurs

Un constructeur de déplacement est une fonction membre spéciale qui déplace la propriété des données d’un objet existant vers une nouvelle variable sans copier les données d’origine. Il prend une référence rvalue comme premier paramètre, et tous les paramètres ultérieurs doivent avoir des valeurs par défaut. Les constructeurs de déplacement peuvent augmenter considérablement l’efficacité de votre programme lors du passage d’objets volumineux.

Box(Box&& other);

Le compilateur choisit un constructeur de déplacement lorsque l’objet est initialisé par un autre objet du même type, si l’autre objet est sur le point d’être détruit et n’a plus besoin de ses ressources. L’exemple suivant montre un cas quand un constructeur de déplacement est sélectionné par la résolution de surcharge. Dans le constructeur qui appelle get_Box(), la valeur retournée est une valeur xvalue (valeur eXpiring). Elle n’est affectée à aucune variable et est donc sur le point d’être hors de portée. Pour fournir une motivation pour cet exemple, nous allons donner à Box un grand vecteur de chaînes qui représentent son contenu. Au lieu de copier le vecteur et ses chaînes, le constructeur de déplacement le « vole » de la valeur arrivant à expiration « box » afin que le vecteur appartient désormais au nouvel objet. L’appel à std::move est nécessaire, car les deux et string les vector classes implémentent leurs propres constructeurs de déplacement.

#include <iostream>
#include <vector>
#include <string>
#include <algorithm>
using namespace std;

class Box {
public:
    Box() { std::cout << "default" << std::endl; }
    Box(int width, int height, int length)
       : m_width(width), m_height(height), m_length(length)
    {
        std::cout << "int,int,int" << std::endl;
    }
    Box(Box& other)
       : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length)
    {
        std::cout << "copy" << std::endl;
    }
    Box(Box&& other) : m_width(other.m_width), m_height(other.m_height), m_length(other.m_length)
    {
        m_contents = std::move(other.m_contents);
        std::cout << "move" << std::endl;
    }
    int Volume() { return m_width * m_height * m_length; }
    void Add_Item(string item) { m_contents.push_back(item); }
    void Print_Contents()
    {
        for (const auto& item : m_contents)
        {
            cout << item << " ";
        }
    }
private:
    int m_width{ 0 };
    int m_height{ 0 };
    int m_length{ 0 };
    vector<string> m_contents;
};

Box get_Box()
{
    Box b(5, 10, 18); // "int,int,int"
    b.Add_Item("Toupee");
    b.Add_Item("Megaphone");
    b.Add_Item("Suit");

    return b;
}

int main()
{
    Box b; // "default"
    Box b1(b); // "copy"
    Box b2(get_Box()); // "move"
    cout << "b2 contents: ";
    b2.Print_Contents(); // Prove that we have all the values

    char ch;
    cin >> ch; // keep window open
    return 0;
}

Si une classe ne définit pas de constructeur de déplacement, le compilateur génère un constructeur implicite s’il n’existe aucun constructeur de copie déclaré par l’utilisateur, opérateur d’assignation de copie, opérateur d’affectation de déplacement ou destructeur. Si aucun constructeur de déplacement explicite ou implicite n’est défini, les opérations qui utilisent autrement un constructeur de déplacement utilisent plutôt le constructeur de copie. Si une classe déclare un constructeur de déplacement ou un opérateur d’affectation de déplacement, le constructeur de copie déclaré implicitement est défini comme supprimé.

Un constructeur de déplacement déclaré implicitement est défini comme supprimé si des membres qui sont des types de classe n’ont pas de destructeur ou si le compilateur ne peut pas déterminer le constructeur à utiliser pour l’opération de déplacement.

Pour plus d’informations sur l’écriture d’un constructeur de déplacement non trivial, consultez Constructeurs de déplacement et Opérateurs d’affectation de déplacement (C++).

Constructeurs explicitement par défaut et supprimés

Vous pouvez explicitement utiliser des constructeurs de copie par défaut , des constructeurs par défaut, des constructeurs de déplacement, des opérateurs d’assignation de copie, des opérateurs d’assignation de déplacement et des destructeurs. Vous pouvez supprimer explicitement toutes les fonctions membres spéciales.

class Box2
{
public:
    Box2() = delete;
    Box2(const Box2& other) = default;
    Box2& operator=(const Box2& other) = default;
    Box2(Box2&& other) = default;
    Box2& operator=(Box2&& other) = default;
    //...
};

Pour plus d’informations, consultez Fonctions explicitement par défaut et supprimées.

constructeurs constexpr

Un constructeur peut être déclaré comme constexpr si

  • elle est déclarée comme par défaut ou répond à toutes les conditions pour les fonctions constexpr en général ;
  • la classe n’a pas de classes de base virtuelles ;
  • chacun des paramètres est un type littéral ;
  • le corps n’est pas un try-block de fonction ;
  • tous les membres de données non statiques et les sous-objets de classe de base sont initialisés ;
  • si la classe est (a) une union ayant des membres variants, ou (b) a des unions anonymes, un seul des membres de l’union est initialisé ;
  • tous les membres de données non statiques du type de classe, et tous les sous-objets de classe de base ont un constructeur constexpr

Constructeurs de liste d’initialiseurs

Si un constructeur prend un std::initializer_list<T> paramètre et que tous les autres paramètres ont des arguments par défaut, ce constructeur est sélectionné dans la résolution de surcharge lorsque la classe est instanciée via l’initialisation directe. Vous pouvez utiliser le initializer_list pour initialiser n’importe quel membre qui peut l’accepter. Par exemple, supposons que la classe Box (affichée précédemment) possède un std::vector<string> membre m_contents. Vous pouvez fournir un constructeur comme suit :

    Box(initializer_list<string> list, int w = 0, int h = 0, int l = 0)
        : m_contents(list), m_width(w), m_height(h), m_length(l)
{}

Puis créez des objets Box comme suit :

    Box b{ "apples", "oranges", "pears" }; // or ...
    Box b2(initializer_list<string> { "bread", "cheese", "wine" }, 2, 4, 6);

Constructeurs explicites

Si une classe a un constructeur avec un paramètre unique, ou si tous les paramètres sauf un ont une valeur par défaut, le type de paramètre peut être implicitement converti en type de classe. Par exemple, si la classe Box a un constructeur semblable au suivant :

Box(int size): m_width(size), m_length(size), m_height(size){}

Il est possible d’initialiser une boîte comme suit :

Box b = 42;

Ou de passer une valeur int à une fonction qui accepte un objet Box :

class ShippingOrder
{
public:
    ShippingOrder(Box b, double postage) : m_box(b), m_postage(postage){}

private:
    Box m_box;
    double m_postage;
}
//elsewhere...
    ShippingOrder so(42, 10.8);

Ces conversions peuvent être utiles dans certains cas mais, le plus souvent, elles peuvent entraîner des erreurs subtiles mais graves dans votre code. En règle générale, vous devez utiliser le explicit mot clé sur un constructeur (et les opérateurs définis par l’utilisateur) pour empêcher ce type de conversion de type implicite :

explicit Box(int size): m_width(size), m_length(size), m_height(size){}

Si le constructeur est explicite, cette ligne entraîne une erreur du compilateur : ShippingOrder so(42, 10.8);. Pour plus d’informations, consultez Conversions de types définis par l’utilisateur.

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. Tout d’abord, le constructeur de base est appelé. Ensuite, les membres de classe de base sont initialisés dans l’ordre dans lequel ils apparaissent dans la déclaration de classe. Enfin, le constructeur dérivé est appelé.

#include <iostream>

using namespace std;

class Contained1 {
public:
    Contained1() { cout << "Contained1 ctor\n"; }
};

class Contained2 {
public:
    Contained2() { cout << "Contained2 ctor\n"; }
};

class Contained3 {
public:
    Contained3() { cout << "Contained3 ctor\n"; }
};

class BaseContainer {
public:
    BaseContainer() { cout << "BaseContainer ctor\n"; }
private:
    Contained1 c1;
    Contained2 c2;
};

class DerivedContainer : public BaseContainer {
public:
    DerivedContainer() : BaseContainer() { cout << "DerivedContainer ctor\n"; }
private:
    Contained3 c3;
};

int main() {
    DerivedContainer dc;
}

Voici le format :

Contained1 ctor
Contained2 ctor
BaseContainer ctor
Contained3 ctor
DerivedContainer ctor

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 dans l’ordre de dérivation, par exemple, s’il ClassA est dérivé ClassBde , qui est dérivé de ClassC, le ClassC constructeur est appelé en premier, puis le ClassB constructeur, puis le ClassA constructeur.

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

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 n’est pas délégué, tous les objets et membres de classe de base entièrement construits sont détruits. Toutefois, étant donné que l’objet lui-même n’est pas entièrement construit, le destructeur n’est pas exécuté.

Constructeurs dérivés et initialisation d’agrégation étendue

Si le constructeur d’une classe de base n’est pas public, mais accessible à une classe dérivée, vous ne pouvez pas utiliser d’accolades vides pour initialiser un objet du type dérivé en /std:c++17 mode et ultérieurement dans Visual Studio 2017 et versions ultérieures.

L’exemple suivant montre le comportement conforme à C++14 :

struct Derived;

struct Base {
    friend struct Derived;
private:
    Base() {}
};

struct Derived : Base {};

Derived d1; // OK. No aggregate init involved.
Derived d2 {}; // OK in C++14: Calls Derived::Derived()
               // which can call Base ctor.

Dans C++17, Derived est désormais considéré comme un type d’agrégat. Cela signifie que l’initialisation de Base par le biais du constructeur par défaut privé se produit donc directement dans le cadre de la règle d’initialisation d’agrégats étendue. Auparavant, le Base constructeur privé a été appelé via le Derived constructeur, et il a réussi en raison de la friend déclaration.

L’exemple suivant montre le comportement C++17 dans Visual Studio 2017 et versions ultérieures en /std:c++17 mode :

struct Derived;

struct Base {
    friend struct Derived;
private:
    Base() {}
};

struct Derived : Base {
    Derived() {} // add user-defined constructor
                 // to call with {} initialization
};

Derived d1; // OK. No aggregate init involved.

Derived d2 {}; // error C2248: 'Base::Base': can't access
               // private member declared in class 'Base'

Constructeurs pour les classes qui ont plusieurs héritages

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 ctor\n"; }
};
class BaseClass2 {
public:
    BaseClass2() { cout << "BaseClass2 ctor\n"; }
};
class BaseClass3 {
public:
    BaseClass3() { cout << "BaseClass3 ctor\n"; }
};
class DerivedClass : public BaseClass1,
                     public BaseClass2,
                     public BaseClass3
                     {
public:
    DerivedClass() { cout << "DerivedClass ctor\n"; }
};

int main() {
    DerivedClass dc;
}

Vous devez obtenir la sortie suivante :

BaseClass1 ctor
BaseClass2 ctor
BaseClass3 ctor
DerivedClass ctor

Constructeurs effectuant une délégation

Un constructeur de délégation appelle un constructeur différent dans la même classe pour effectuer une partie du travail d’initialisation. Cette fonctionnalité est utile lorsque vous avez plusieurs constructeurs qui doivent tous effectuer un travail similaire. Vous pouvez écrire la logique principale dans un constructeur et l’appeler à partir d’autres. Dans l’exemple trivial suivant, Box(int) délègue son travail à Box(int,int,int)) :

class Box {
public:
    // Default constructor
    Box() {}

    // Initialize a Box with equal dimensions (i.e. a cube)
    Box(int i) :  Box(i, i, i)  // delegating constructor
    {}

    // Initialize a Box with custom dimensions
    Box(int width, int length, int height)
        : m_width(width), m_length(length), m_height(height)
    {}
    //... rest of class as before
};

L'objet créé par les constructeurs est entièrement initialisé dès qu'un constructeur est terminé. Pour plus d’informations, consultez Délégation des constructeurs.

Constructeurs d'héritage (C++11)

Une classe dérivée peut hériter des constructeurs d’une classe de base directe à l’aide d’une using déclaration comme indiqué dans l’exemple suivant :

#include <iostream>
using namespace std;

class Base
{
public:
    Base() { cout << "Base()" << endl; }
    Base(const Base& other) { cout << "Base(Base&)" << endl; }
    explicit Base(int i) : num(i) { cout << "Base(int)" << endl; }
    explicit Base(char c) : letter(c) { cout << "Base(char)" << endl; }

private:
    int num;
    char letter;
};

class Derived : Base
{
public:
    // Inherit all constructors from Base
    using Base::Base;

private:
    // Can't initialize newMember from Base constructors.
    int newMember{ 0 };
};

int main()
{
    cout << "Derived d1(5) calls: ";
    Derived d1(5);
    cout << "Derived d1('c') calls: ";
    Derived d2('c');
    cout << "Derived d3 = d2 calls: " ;
    Derived d3 = d2;
    cout << "Derived d4 calls: ";
    Derived d4;
}

/* Output:
Derived d1(5) calls: Base(int)
Derived d1('c') calls: Base(char)
Derived d3 = d2 calls: Base(Base&)
Derived d4 calls: Base()*/

Visual Studio 2017 et versions ultérieures : l’instruction using en /std:c++17 mode et ultérieure apporte dans l’étendue tous les constructeurs de la classe de base, sauf ceux qui ont une signature identique aux constructeurs dans la classe dérivée. En général, il est préférable d’utiliser des constructeurs héritants lorsque la classe dérivée ne déclare aucun nouveau membre de données ou constructeur.

Un modèle de classe peut hériter de tous les constructeurs d'un argument de type si ce type spécifie une classe de base :

template< typename T >
class Derived : T {
    using T::T;   // declare the constructors from T
    // ...
};

Une classe dérivant ne peut pas hériter de plusieurs classes de base si ces classes de base ont des constructeurs qui ont une signature identique.

Constructeurs et classes composites

Les classes qui contiennent des membres de type classe sont appelées 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"});
}

Dans cette section

Voir aussi

Classes et structs