Поделиться через


Конструкторы (C++)

Конструктор — это специальная функция-член, которая инициализирует экземпляр класса. Для вызова конструктора используется имя класса вместе с параметрами, заключенными в фигурные или круглые скобки.

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

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

Порядок создания

Конструктор выполняет свою работу в следующем порядке.

  1. Вызывает конструкторы базовых классов и членов в порядке объявления.

  2. Если класс является производным от виртуальных базовых классов, конструктор инициализирует указатели виртуальных базовых классов объекта.

  3. Если класс имеет или наследует виртуальные функции, конструктор инициализирует указатели виртуальных функций объекта. Указатели виртуальных функций указывают на таблицу виртуальных функций класса, чтобы обеспечить правильную привязку вызовов виртуальных функций к коду.

  4. Выполняет весь код в теле функции.

В следующем примере показан порядок, в котором конструкторы базовых классов и конструкторы-члены вызываются в конструкторе производного класса. Сначала вызывается конструктор базового класса, затем инициализируются члены базового класса в порядке их появления в объявлении класса. После этого вызывается конструктор производного класса.

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

Выходные данные этого кода:

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

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

  1. Отменяется код в теле функции конструктора.

  2. Объекты базовых классов и объекты-члены удаляются в порядке, обратном объявлению.

  3. Если конструктор не является делегирующим, удаляются все полностью созданные объекты базовых классов и объекты-члены. Однако поскольку сам объект создан не полностью, деструктор не выполняется.

Явные конструкторы

Если конструктор имеет только один параметр или все его параметры, кроме одного, имеют значения по умолчанию, можно предотвратить неявные преобразования типов в конструкторе с помощью ключевого слова explicit. Для получения дополнительной информации см. Конструкторы (C++).

Конструкторы по умолчанию

Конструкторы по умолчанию (то есть конструкторы, не имеющие параметров) следуют несколько иным правилам.

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

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

int main(){

    Box box1{};
    Box box2;
}

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

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

Это пример проблемы Most Vexing Parse (наиболее неоднозначного анализа). Поскольку выражение примера можно интерпретировать как объявление функции или как вызов конструктора по умолчанию и в связи с тем, что средства синтаксического анализа C++ отдают предпочтение объявлениям перед другими действиями, данное выражение обрабатывается как объявление функции. Дополнительные сведения см. в статье Most Vexing Parse.

В случае явного объявления конструкторов компилятор не предоставляет конструктор по умолчанию:

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
}

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

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

Однако для инициализации массива Boxes можно использовать набор списков инициализаторов:

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

Конструкторы копии и перемещения

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

Конструктор перемещения обеспечивает передачу выделенной памяти от одного объекта к другому. Для получения дополнительной информации см. Практическое руководство. Написание конструктора перемещений.

Явно заданные и удаленные конструкторы

Можно явно создавать по умолчанию конструкторы копии, конструкторы по умолчанию, операторы присваивания копий и деструкторы, однако явные значения по умолчанию для конструкторов перемещения и операторов присваивания перемещения не поддерживаются. (Это поддерживается в Visual Studio 2015.) Явное удаление можно осуществлять для всех специальных функций. Для получения дополнительной информации см. Явно используемые по умолчанию и удаленные функции.

Конструкторы в производных классах

Конструктор производного класса всегда вызывает конструктор базового класса, чтобы перед выполнением любых дополнительных операций иметь в своем распоряжении полностью созданные базовые классы. Конструкторы базовых классов вызываются в порядке наследования — например, если ClassA является производным от ClassB, который является производным от ClassC, сначала вызывается конструктор ClassC, затем конструктор ClassB и последним конструктор ClassA.

Если базовый класс не имеет конструктор по умолчанию, в конструкторе производного класса необходимо указать параметры конструктора базового класса.

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

Конструкторы классов с множественным наследованием

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

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

Должны выводиться следующие выходные данные:

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

Виртуальные функции в конструкторах

Рекомендуется соблюдать осторожность при вызове виртуальных функций в конструкторах. Поскольку конструктор базового класса всегда вызывается перед конструктором производного класса, функция, вызываемая в конструкторе базового класса, является версией базового, а не производного класса. В следующем примере создание объекта DerivedClass приводит к выполнению реализации объекта BaseClass функции print_it() перед тем, как конструктор DerivedClass вызовет выполнение реализации DerivedClass 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;
}

Выходные данные этого кода:

BaseClass print_it
Derived Class print_it

Конструкторы и составные классы

Классы, содержащие члены типа класса, называются составными классами. При создании члена типа класса составного класса конструктор вызывается перед собственным конструктором класса. Если у содержащегося класса нет конструктора по умолчанию, необходимо использовать список инициализации в конструкторе составного класса. В предыдущем примере StorageBox при присвоении типу переменной-члена m_label нового класса Label необходимо вызвать конструктор базового класса и инициализировать переменную m_label в конструкторе 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"});
}

Делегирующие конструкторы

Делегирующий конструктор вызывает другой конструктор в том же классе для выполнения некоторых операций инициализации. В следующем примере производный класс содержит три конструктора: второй конструктор делегирует работу первому, а третий — второму.

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

Выходные данные этого кода:

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

Объект, созданный конструкторами, полностью инициализируется сразу после выполнения любого конструктора. DerivedContainer(int int1) выполняется успешно, но DerivedContainer(int int1, int int2) завершается сбоем, и вызывается деструктор.

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

Результат

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

Дополнительные сведения см. в разделе Единообразная инициализация и делегирование конструкторов.

См. также

Ссылки

Специальные функции-члены (C++)