Инициализаторы

Инициализатор определяет начальное значение переменной. Можно инициализировать переменные в этих контекстах:

  • В определении переменной:

    int i = 3;
    Point p1{ 1, 2 };
    
  • В качестве одного из параметров функции:

    set_point(Point{ 5, 6 });
    
  • В качестве возвращаемого типа функции:

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

Инициализаторы могут принимать эти формы:

  • Выражение (или разделенный запятыми список выражений) в скобках:

    Point p1(1, 2);
    
  • Знак равенства с последующим выражением:

    string s = "hello";
    
  • Список инициализации в фигурных скобках. Список может быть пустым или может состоять из набора списков как в приведенном ниже примере.

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

Типы инициализации

Существует несколько типов инициализации, которые могут встречаться на различных этапах выполнения программы. Различные виды инициализации не являются взаимоисключающими, например, инициализация списка может активировать инициализацию значений и в других обстоятельствах, что может активировать статистическую инициализацию.

Нулевая инициализация

Нулевая инициализация — задание для переменной нулевого значения, неявно преобразованного в тип:

  • Числовые переменные инициализируются значением 0 (или 0,0; 0,0000000000 и т.п.).

  • Переменные char инициализированы в '\0'.

  • Указатели инициализированы в nullptr.

  • Массивы, классы POD, структуры и объединения инициализируют свои члены до нуля.

Нулевая инициализация выполняется в разное время:

  • При запуске программы — для всех именованных переменных, имеющих статическую длительность. Далее эти переменные могут быть инициализированы повторно.

  • Во время инициализации значений — для скалярных типов и типов класса POD, которые инициализируются с помощью пустых фигурных скобок.

  • Для массивов, у которых инициализировано только подмножество членов.

Ниже приведены некоторые примеры нулевой инициализации:

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'
}

Инициализация по умолчанию

Инициализация по умолчанию для классов, структур и объединений — это инициализация с помощью конструктора по умолчанию. Конструктор по умолчанию можно вызывать без выражения инициализации или с new помощью ключевое слово:

MyClass mc1;
MyClass* mc3 = new MyClass;

Если у класса, структуры или объединения нет конструктора по умолчанию, компилятор выдает ошибку.

Скалярные переменные по умолчанию инициализированы, если они определены без выражения инициализации. Они имеют неопределенные значения.

int i1;
float f;
char c;

Массивы по умолчанию инициализированы при определении без выражения инициализации. Если массив инициализируется по умолчанию, его члены инициализируются по умолчанию и приобретают неопределенные значения как в приведенном ниже примере.

int int_arr[3];

Если элементы массива не имеют конструктора по умолчанию, компилятор выдает ошибку.

Инициализация по умолчанию константных переменных

Константные переменные необходимо объявлять вместе с инициализатором. Если они скалярные типы, они вызывают ошибку компилятора и если они типы классов с конструктором по умолчанию, они вызывают предупреждение:

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
}

Инициализация по умолчанию статических переменных

Статические переменные, объявленные без инициализатора, инициализируются значением 0 (с неявным преобразованием к соответствующему типу).

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'}
}

Дополнительные сведения об инициализации глобальных статических объектов см. в разделе "Основные аргументы функции и командной строки".

Инициализация значения

Инициализация значения происходит в следующих случаях:

  • Именованное значение инициализируется с использованием пустых фигурных скобок.

  • Анонимный временный объект инициализируется с помощью пустых круглых или фигурных скобок.

  • Объект инициализирован с помощью new ключевое слово плюс пустые скобки или скобки

При инициализации значения выполняются следующие действия:

  • Для классов, имеющих хотя бы один открытый конструктор, вызывается конструктор по умолчанию.

  • для классов nonunion без объявленных конструкторов объект инициализирован с нуля и вызывается конструктор по умолчанию.

  • В случае массивов каждый элемент инициализируется значением.

  • Во всех остальных случаях переменная инициализируется нулевым значением.

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
}

Инициализация копированием

Инициализация копированием — это инициализация одного объекта с использованием другого объекта. Она выполняется в следующих случаях:

  • Переменная инициализируется с помощью знака равенства.

  • Аргумент передается в функцию.

  • Объект возвращается функцией.

  • Возникает или перехватывается исключение.

  • Нестатический элемент данных инициализируется с помощью знака равенства.

  • Класс, структура и члены объединения инициализируются с применением инициализации путем копирования во время агрегатной инициализации. Примеры инициализации агрегата см. в разделе "Агрегатная инициализация ".

Следующий код демонстрирует несколько примеров инициализации копированием.

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

Инициализация копирования не может вызывать явные конструкторы.

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

В некоторых случаях, если конструктор копии класса удален или недоступен, копируемая инициализация вызывает ошибку компилятора.

Прямая инициализация

Прямая инициализация — это инициализация с использованием (непустых) круглых или фигурных скобок. В отличие от копируемой инициализации она может вызывать явные конструкторы. Она выполняется в следующих случаях:

  • Переменная инициализируется с помощью непустых круглых или фигурных скобок.

  • Переменная инициализирована с new помощью ключевое слово плюс непустые скобки или скобки

  • Переменная инициализируется с помощью static_cast

  • В конструкторе базовые классы и нестатические члены инициализируются с помощью списка инициализации.

  • В копии захваченной переменной в лямбда-выражении.

Приведенный ниже код демонстрирует несколько примеров прямой инициализации.

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

Инициализация списком

Инициализация списком выполняется, когда переменная инициализируется с помощью списка инициализации в фигурных скобках. Списки инициализации в фигурных скобках можно использовать в следующих случаях:

  • Инициализируется переменная.

  • Класс инициализируется с помощью new ключевое слово

  • Объект возвращается функцией.

  • Аргумент передается функции.

  • Один из аргументов при прямой инициализации.

  • В инициализаторе нестатических элементов данных.

  • В списке инициализации конструктора.

Приведенный ниже код демонстрирует несколько примеров инициализации списком.

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

Агрегатная инициализация

Агрегатная инициализация — форма инициализации списка для массивов и типов классов (часто структур и объединений), со следующими характеристиками:

  • Отсутствие закрытых или защищенных членов.

  • Отсутствие заданных пользователем конструкторов кроме явно заданных по умолчанию или удаленных конструкторов.

  • Отсутствие базовых классов.

  • Отсутствие виртуальных функций-членов.

Примечание.

В Visual Studio 2015 и более ранних версиях агрегат не может иметь инициализаторы фигурных скобок или равных значений для нестатических элементов. Это ограничение было удалено в стандарте C++14 и реализовано в Visual Studio 2017.

Агрегатные инициализаторы состоят из списка инициализации в фигурных скобках со знаком равенства или без него как в приведенном ниже примере:

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

Должен появиться следующий результат:

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

Важно!

Элементы массива, объявленные, но не явно инициализированы во время статистической инициализации, как показано выше myArr3 .

Инициализация объединений и структур

Если у объединения нет конструктора, его можно инициализировать с одним значением (или с другим экземпляром объединения). Значение используется для инициализации первого нестатического поля. Это отличается от инициализации структур, где первое значение в инициализаторе используется для инициализации первого поля, второе — для инициализации второго поля и т. д. Сравните инициализацию объединений и структур в следующем примере:

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'
}

Инициализация статистических выражений, содержащих статистические выражения

Агрегатные типы могут содержать другие агрегатные типы, например массивы массивов, массивы структур и т. п. Эти типы инициализируются с помощью вложенных наборов фигурных скобок, как показано в следующем примере:

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

Инициализация ссылок

Переменные ссылочного типа должны инициализироваться объектом типа, на котором основан ссылочный тип, или объектом типа, который можно преобразовать в такой тип. Например:

// 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
}

Единственный способ инициализировать ссылку с помощью временного объекта является инициализация постоянного временного объекта. После инициализации переменная ссылочного типа всегда указывает на тот же объект; Его нельзя изменить, чтобы указать на другой объект.

Хотя синтаксис может быть одинаковым, инициализация переменных ссылочного типа и присваивание значений переменным ссылочного типа семантически различаются. В предыдущем примере присваивания, которые изменяют значения переменных iVar и lVar, выглядят аналогично инициализации, но имеют другой эффект. Инициализация определяет объект, на который указывает переменная ссылочного типа; при присваивании через ссылку производится присваивание значения объекту, на который указывает ссылка.

Поскольку передача аргумента ссылочного типа в функцию и возврат значения ссылочного типа из функции являются инициализацией, формальные аргументы функции, а также возвращаемые ссылки инициализируются правильно.

Переменные ссылочного типа можно объявлять без инициализаторов только в указанных ниже случаях.

  • Объявления функций (прототипы). Например:

    int func( int& );
    
  • Объявления типов значений, возвращаемых функцией. Например:

    int& func( int& );
    
  • Объявления члена класса ссылочного типа. Например:

    class c {public:   int& i;};
    
  • Объявление переменной, явно указанной как extern. Например:

    extern int& iVal;
    

При инициализации переменной ссылочного типа компилятор использует граф принятия решений, показанный на следующем рисунке, для выбора между созданием ссылки на объект или созданием временного объекта, к которому указываются эталонные точки:

Decision graph for initialization of reference types.

График принятия решений начинается с: является ли инициализатор lvalue того же типа или типом, производным от типа ссылки? Если да, ссылка ссылается на объект, указанный в инициализаторе. Если нет, следующее решение заключается в том, является ли переменная ссылочного типа инициализированной ссылкой const T, и может ли инициализатор быть неявно преобразован в T? Если да, создается временная переменная, а эталонная переменная становится именем для этого временного. Если нет, это ошибка.

Граф принятия решений для инициализации ссылочных типов

Ссылки на volatile типы (объявленные как volatiletypename&identifier) можно инициализировать с volatile объектами того же типа или объектами, которые не были объявлены как .volatile Однако они не могут быть инициализированы с const объектами этого типа. Аналогичным образом ссылки на const типы (объявленные как constидентификатор typename)) можно инициализировать с const объектами того же типа (или что-либо, которое имеет преобразование в этот тип или объекты, которые не были объявлены как ).const Однако они не могут быть инициализированы с volatile объектами этого типа.

Ссылки, которые не являются квалифицированными с const помощью или volatile ключевое слово, можно инициализировать только с объектами, объявленными как ни один из constvolatileних.

Инициализация внешних переменных

Объявления автоматических, статических и внешних переменных могут содержать инициализаторы. Однако объявления внешних переменных могут содержать инициализаторы, только если переменные не объявлены как extern.