Initialiseurs

Un initialiseur spécifie la valeur initiale d'une variable. Vous pouvez initialiser des variables dans les contextes suivants :

  • Dans la définition d'une variable :

    int i = 3;
    Point p1{ 1, 2 };
    
  • Comme l'un des paramètres d'une fonction :

    set_point(Point{ 5, 6 });
    
  • En tant que valeur de retour d'une fonction :

    Point get_new_point(int x, int y) { return { x, y }; }
    Point get_new_point(int x, int y) { return Point{ x, y }; }
    

Les initialiseurs peuvent prendre les formes suivantes :

  • Une expression (ou une liste d'expressions séparées par des virgules) entre parenthèses :

    Point p1(1, 2);
    
  • Un signe égal suivi d'une expression :

    string s = "hello";
    
  • Une liste d'initialiseurs entre accolades. La liste peut être vide ou comprendre un ensemble de listes, comme dans l'exemple suivant :

    struct Point{
        int x;
        int y;
    };
    class PointConsumer{
    public:
        void set_point(Point p){};
        void set_points(initializer_list<Point> my_list){};
    };
    int main() {
        PointConsumer pc{};
        pc.set_point({});
        pc.set_point({ 3, 4 });
        pc.set_points({ { 3, 4 }, { 5, 6 } });
    }
    

Types d'initialisations

Il existe plusieurs types d'initialisations, qui peuvent se produire à différents moments durant l'exécution du programme. Différents types d’initialisation ne s’excluent pas mutuellement. Par exemple, l’initialisation de liste peut déclencher l’initialisation de valeur et, dans d’autres circonstances, elle peut déclencher l’initialisation d’agrégation.

Initialisation à zéro

L'initialisation à zéro attribue à une variable une valeur zéro convertie implicitement en type :

  • Les variables numériques sont initialisées à 0 (ou 0,0, ou 0,0000000000, etc.).

  • Les variables char sont initialisées sur '\0'.

  • Les pointeurs sont initialisés vers nullptr.

  • Les tableaux, les classes POD , les structs et les unions ont leurs membres initialisés avec une valeur nulle.

L'initialisation à zéro peut être effectuée à différents moments :

  • Au démarrage du programme, pour toutes les variables nommées dont la durée est statique. Ces variables pourront être initialisées à nouveau.

  • Pendant l'initialisation de valeurs, pour les types scalaires et ceux de classe POD qui sont initialisés à l'aide d'accolades vides.

  • Pour les tableaux dont seul un sous-ensemble de leurs membres est initialisé.

Voici quelques exemples d'initialisation à zéro :

struct my_struct{
    int i;
    char c;
};

int i0;              // zero-initialized to 0
int main() {
    static float f1;  // zero-initialized to 0.000000000
    double d{};     // zero-initialized to 0.00000000000000000
    int* ptr{};     // initialized to nullptr
    char s_array[3]{'a', 'b'};  // the third char is initialized to '\0'
    int int_array[5] = { 8, 9, 10 };  // the fourth and fifth ints are initialized to 0
    my_struct a_struct{};   // i = 0, c = '\0'
}

Initialisation par défaut

L'initialisation par défaut des classes, des structs et des unions utilise un constructeur par défaut. Le constructeur par défaut peut être appelé sans expression d’initialisation ou avec l’mot clé new :

MyClass mc1;
MyClass* mc3 = new MyClass;

Si la classe, le struct ou l’union n’a pas de constructeur par défaut, le compilateur émet une erreur.

Les variables scalaires sont initialisées par défaut lorsqu’elles sont définies sans expression d’initialisation. Leurs valeurs sont indéterminées.

int i1;
float f;
char c;

Les tableaux sont initialisés par défaut lorsqu’ils sont définis sans expression d’initialisation. Lorsqu'un tableau est initialisé par défaut, ses membres sont initialisés par défaut et ont des valeurs indéterminées, comme dans l'exemple suivant :

int int_arr[3];

Si les membres du tableau n’ont pas de constructeur par défaut, le compilateur émet une erreur.

Initialisation par défaut des variables constantes

Les variables constantes doivent être déclarées en même temps que l'initialiseur. S’ils sont des types scalaires, ils provoquent une erreur du compilateur et s’ils sont des types de classes qui ont un constructeur par défaut, ils provoquent un avertissement :

class MyClass{};
int main() {
    //const int i2;   // compiler error C2734: const object must be initialized if not extern
    //const char c2;  // same error
    const MyClass mc1; // compiler error C4269: 'const automatic data initialized with compiler generated default constructor produces unreliable results
}

Initialisation par défaut des variables statiques

Les variables statiques déclarées sans initialiseur sont initialisées à 0 (implicitement converties en type).

class MyClass {
private:
    int m_int;
    char m_char;
};

int main() {
    static int int1;       // 0
    static char char1;     // '\0'
    static bool bool1;   // false
    static MyClass mc1;     // {0, '\0'}
}

Pour plus d’informations sur l’initialisation d’objets statiques globaux, consultez les arguments de fonction et de ligne de commande principaux.

Initialisation de valeurs

L'initialisation de valeurs se produit dans les cas suivants :

  • une valeur nommée est initialisée à l'aide d'accolades vides ;

  • un objet temporaire anonyme est initialisé à l'aide de parenthèses ou d'accolades vides ;

  • un objet est initialisé avec le new mot clé plus des parenthèses ou accolades vides

L'initialisation de valeurs entraîne ce qui suit :

  • pour les classes qui possèdent au moins un constructeur public, le constructeur par défaut est appelé ;

  • pour les classes nonunion sans constructeur déclaré, l’objet est initialisé zéro et le constructeur par défaut est appelé

  • pour les tableaux, chaque élément est initialisé par une valeur ;

  • dans tous les autres cas, la variable est initialisée à zéro.

class BaseClass {
private:
    int m_int;
};

int main() {
    BaseClass bc{};     // class is initialized
    BaseClass*  bc2 = new BaseClass();  // class is initialized, m_int value is 0
    int int_arr[3]{};  // value of all members is 0
    int a{};     // value of a is 0
    double b{};  // value of b is 0.00000000000000000
}

Initialisation de copie

L'initialisation de copie correspond à l'initialisation d'un objet à l'aide d'un autre objet. Elle se produit dans les cas suivants :

  • Une variable est initialisée à l'aide d'un signe égal.

  • Un argument est passé à une fonction.

  • Un objet est retourné par une fonction.

  • Une exception est levée ou interceptée.

  • Des données membres non statiques sont initialisées à l'aide d'un signe égal.

  • Les membres des classes, des structs et des unions sont initialisés par initialisation de copie pendant l'initialisation des agrégats. Consultez l’initialisation d’agrégation pour obtenir des exemples.

Le code suivant montre plusieurs exemples d'initialisation de copie :

#include <iostream>
using namespace std;

class MyClass{
public:
    MyClass(int myInt) {}
    void set_int(int myInt) { m_int = myInt; }
    int get_int() const { return m_int; }
private:
    int m_int = 7; // copy initialization of m_int

};
class MyException : public exception{};
int main() {
    int i = 5;              // copy initialization of i
    MyClass mc1{ i };
    MyClass mc2 = mc1;      // copy initialization of mc2 from mc1
    MyClass mc1.set_int(i);    // copy initialization of parameter from i
    int i2 = mc2.get_int(); // copy initialization of i2 from return value of get_int()

    try{
        throw MyException();
    }
    catch (MyException ex){ // copy initialization of ex
        cout << ex.what();
    }
}

L’initialisation de copie ne peut pas appeler de constructeurs explicites.

vector<int> v = 10; // the constructor is explicit; compiler error C2440: can't convert from 'int' to 'std::vector<int,std::allocator<_Ty>>'
regex r = "a.*b"; // the constructor is explicit; same error
shared_ptr<int> sp = new int(1729); // the constructor is explicit; same error

Dans certains cas, si le constructeur de copie de la classe est supprimé ou inaccessible, l'initialisation de copie entraîne une erreur du compilateur.

Initialisation directe

L'initialisation directe utilise des accolades ou des parenthèses non vides. Contrairement à l'initialisation de copie, elle peut appeler des constructeurs explicites. Elle se produit dans les cas suivants :

  • Une variable est initialisée à l'aide d'accolades ou de parenthèses non vides.

  • une variable est initialisée avec le new mot clé plus des accolades ou parenthèses non vides

  • une variable est initialisée avec static_cast

  • Dans un constructeur, les classes de base et les membres non statiques sont initialisés à l'aide d'une liste d'initialiseurs.

  • Dans la copie d’une variable capturée d’une expression lambda.

Le code suivant montre des exemples d'initialisation directe :

class BaseClass{
public:
    BaseClass(int n) :m_int(n){} // m_int is direct initialized
private:
    int m_int;
};

class DerivedClass : public BaseClass{
public:
    // BaseClass and m_char are direct initialized
    DerivedClass(int n, char c) : BaseClass(n), m_char(c) {}
private:
    char m_char;
};
int main(){
    BaseClass bc1(5);
    DerivedClass dc1{ 1, 'c' };
    BaseClass* bc2 = new BaseClass(7);
    BaseClass bc3 = static_cast<BaseClass>(dc1);

    int a = 1;
    function<int()> func = [a](){  return a + 1; }; // a is direct initialized
    int n = func();
}

Initialisation de liste

L'initialisation de liste se produit lorsqu'une variable est initialisée à l'aide d'une liste d'initialiseurs entre accolades. Les listes d'initialiseurs entre accolades peuvent être utilisées dans les cas suivants :

  • Une variable est initialisée.

  • une classe est initialisée avec le new mot clé

  • Un objet est retourné par une fonction.

  • Un argument est passé à une fonction.

  • L’un des arguments est compris dans une initialisation directe.

  • Dans un initialiseur de données membres non statiques.

  • Dans une liste d'initialiseurs de constructeur.

Le code suivant montre des exemples d'initialisation de liste :

class MyClass {
public:
    MyClass(int myInt, char myChar) {}
private:
    int m_int[]{ 3 };
    char m_char;
};
class MyClassConsumer{
public:
    void set_class(MyClass c) {}
    MyClass get_class() { return MyClass{ 0, '\0' }; }
};
struct MyStruct{
    int my_int;
    char my_char;
    MyClass my_class;
};
int main() {
    MyClass mc1{ 1, 'a' };
    MyClass* mc2 = new MyClass{ 2, 'b' };
    MyClass mc3 = { 3, 'c' };

    MyClassConsumer mcc;
    mcc.set_class(MyClass{ 3, 'c' });
    mcc.set_class({ 4, 'd' });

    MyStruct ms1{ 1, 'a', { 2, 'b' } };
}

Initialisation d’agrégation

L'initialisation d'agrégats est un type d'initialisation de liste utilisé pour les tableaux et les types de classes (souvent des structs ou des unions) qui n'ont :

  • aucun membre privé ou protégé ;

  • aucun constructeur fourni par l'utilisateur, à l'exception des constructeurs supprimés ou utilisés par défaut de manière explicite ;

  • aucune classe de base ;

  • aucune fonction membre virtuelle ;

Remarque

Dans Visual Studio 2015 et versions antérieures, un agrégat n’est pas autorisé à avoir des initialiseurs accolades ou égales pour les membres non statiques. Cette restriction a été supprimée dans la norme C++14 et implémentée dans Visual Studio 2017.

Les initialiseurs d'agrégats se composent d'une liste d'initialisation entre accolades, avec ou sans signe égal, comme dans l'exemple suivant :

#include <iostream>
using namespace std;

struct MyAggregate{
    int myInt;
    char myChar;
};

struct MyAggregate2{
    int myInt;
    char myChar = 'Z'; // member-initializer OK in C++14
};

int main() {
    MyAggregate agg1{ 1, 'c' };
    MyAggregate2 agg2{2};
    cout << "agg1: " << agg1.myChar << ": " << agg1.myInt << endl;
    cout << "agg2: " << agg2.myChar << ": " << agg2.myInt << endl;

    int myArr1[]{ 1, 2, 3, 4 };
    int myArr2[3] = { 5, 6, 7 };
    int myArr3[5] = { 8, 9, 10 };

    cout << "myArr1: ";
    for (int i : myArr1){
        cout << i << " ";
    }
    cout << endl;

    cout << "myArr3: ";
    for (auto const &i : myArr3) {
        cout << i << " ";
    }
    cout << endl;
}

Vous devez normalement voir la sortie suivante.

agg1: c: 1
agg2: Z: 2
myArr1: 1 2 3 4
myArr3: 8 9 10 0 0

Important

Les membres du tableau déclarés, mais qui ne sont pas explicitement initialisés pendant l’initialisation d’agrégation, sont initialisés zéro, comme indiqué ci-dessus myArr3 .

Initialisation des unions et des structs

Si une union n’a pas de constructeur, vous pouvez l’initialiser avec une seule valeur (ou avec une autre instance d’une union). La valeur est utilisée pour initialiser le premier champ non static. C'est donc différent de l'initialisation de struct, durant laquelle la première valeur de l'initialiseur est utilisée pour initialiser le premier champ, la deuxième pour initialiser le deuxième champ, et ainsi de suite. L'exemple suivant vous permet de comparer l'initialisation des unions et des structs :

struct MyStruct {
    int myInt;
    char myChar;
};
union MyUnion {
    int my_int;
    char my_char;
    bool my_bool;
    MyStruct my_struct;
};

int main() {
    MyUnion mu1{ 'a' };  // my_int = 97, my_char = 'a', my_bool = true, {myInt = 97, myChar = '\0'}
    MyUnion mu2{ 1 };   // my_int = 1, my_char = 'x1', my_bool = true, {myInt = 1, myChar = '\0'}
    MyUnion mu3{};      // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
    MyUnion mu4 = mu3;  // my_int = 0, my_char = '\0', my_bool = false, {myInt = 0, myChar = '\0'}
    //MyUnion mu5{ 1, 'a', true };  // compiler error: C2078: too many initializers
    //MyUnion mu6 = 'a';            // compiler error: C2440: cannot convert from 'char' to 'MyUnion'
    //MyUnion mu7 = 1;              // compiler error: C2440: cannot convert from 'int' to 'MyUnion'

    MyStruct ms1{ 'a' };            // myInt = 97, myChar = '\0'
    MyStruct ms2{ 1 };              // myInt = 1, myChar = '\0'
    MyStruct ms3{};                 // myInt = 0, myChar = '\0'
    MyStruct ms4{1, 'a'};           // myInt = 1, myChar = 'a'
    MyStruct ms5 = { 2, 'b' };      // myInt = 2, myChar = 'b'
}

Initialisation d'agrégats contenant des agrégats

Les types d'agrégats peuvent contenir d'autres types d'agrégats. Par exemple, les tableaux peuvent contenir des tableaux, des structs, etc. Ces types sont initialisés à l'aide de jeux imbriqués d'accolades, par exemple :

struct MyStruct {
    int myInt;
    char myChar;
};
int main() {
    int intArr1[2][2]{{ 1, 2 }, { 3, 4 }};
    int intArr3[2][2] = {1, 2, 3, 4};
    MyStruct structArr[]{ { 1, 'a' }, { 2, 'b' }, {3, 'c'} };
}

Initialisation de références

Les variables de type référence doivent être initialisées avec un objet du type à partir duquel le type référence est dérivé, ou avec un objet d'un type pouvant être converti en type à partir duquel le type référence est dérivé. Par exemple :

// initializing_references.cpp
int iVar;
long lVar;
int main()
{
    long& LongRef1 = lVar;        // No conversion required.
    long& LongRef2 = iVar;        // Error C2440
    const long& LongRef3 = iVar;  // OK
    LongRef1 = 23L;               // Change lVar through a reference.
    LongRef2 = 11L;               // Change iVar through a reference.
    LongRef3 = 11L;               // Error C3892
}

La seule façon d'initialiser une référence avec un objet temporaire consiste à initialiser un objet constant temporaire. Une fois initialisée, une variable de type référence pointe toujours vers le même objet ; il ne peut pas être modifié pour pointer vers un autre objet.

Bien que la syntaxe puisse être identique, l'initialisation des variables de type référence et l'assignation à des variables de type référence sont sémantiquement différentes. Dans l'exemple précédent, les assignations qui modifient iVar et lVar ressemblent aux initialisations, mais leurs effets sont différents. L'initialisation spécifie l'objet vers lequel pointe la variable de type référence ; l'assignation assigne à l'objet référencé via la référence.

Étant donné que le passage d'un argument de type référence à une fonction et le retour d'une valeur de type référence à partir d'une fonction sont des initialisations, les arguments formels d'une fonction sont initialisés correctement, de même que les références retournées.

Les variables de type référence peuvent être déclarées sans initialiseurs uniquement dans les éléments suivants :

  • Déclarations de fonction (prototypes). Par exemple :

    int func( int& );
    
  • Déclarations de type retour de fonction. Par exemple :

    int& func( int& );
    
  • Déclaration d'un membre de classe de type référence. Par exemple :

    class c {public:   int& i;};
    
  • Déclaration d’une variable explicitement spécifiée en tant que extern. Par exemple :

    extern int& iVal;
    

Lors de l’initialisation d’une variable de type référence, le compilateur utilise le graphique de décision indiqué dans la figure suivante pour sélectionner entre la création d’une référence à un objet ou la création d’un objet temporaire auquel les points de référence sont les suivants :

Decision graph for initialization of reference types.

Le graphique de décision commence par : l’initialiseur est-il un lvalue du même type ou un type dérivé du type de référence ? Si c’est le cas, la référence fait référence à l’objet spécifié dans l’initialiseur. Si ce n’est pas le cas, la décision suivante est de savoir si la variable de type référence est une référence const T initialisée et si l’initialiseur peut-il être converti implicitement en T ? Si c’est le cas, le temporaire est créé et la variable de référence devient un nom pour ce temporaire. Si ce n’est pas le cas, il s’agit d’une erreur.

Graphique de décision pour l’initialisation des types de référence

Les références aux volatile types (déclarées en tant qu’identificateur volatilede nom_type) peuvent être initialisées avec volatile des objets du même type ou avec des objets qui n’ont pas été déclarés comme .volatile Toutefois, ils ne peuvent pas être initialisés avec const des objets de ce type. De même, les références aux const types (déclarées en tant qu’identificateur constde nom_type) peuvent être initialisées avec const des objets du même type (ou tout ce qui a une conversion vers ce type ou avec des objets qui n’ont pas été déclarés comme ).const Toutefois, ils ne peuvent pas être initialisés avec volatile des objets de ce type.

Les références qui ne sont pas qualifiées avec la ou volatile les const mot clé peuvent être initialisées uniquement avec les objets déclarés comme ni ni const ni volatile.

Initialisation des variables externes

Les déclarations de variables automatiques, statiques et externes peuvent contenir des initialiseurs. Toutefois, les déclarations de variables externes peuvent contenir des initialiseurs uniquement si les variables ne sont pas déclarées comme extern.