Konstruktory (C++)
Konstruktor je speciální členská funkce, která inicializuje novou instanci své třídy.Chcete-li volat konstruktor, použijte název třídy s parametry, které jsou uzavřeny v jednoduchých nebo složených závorkách.
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 });
}
Další informace naleznete v tématu Pravidla deklarace konstruktorů.Informace o inicializaci naleznete v tématu Inicializace.
Pořadí vytváření
Konstruktor provádí svou práci v tomto pořadí:
Volá základní třídu a členské konstruktory v pořadí deklarace.
Pokud je třída odvozena z virtuálních základních tříd, inicializuje virtuální základní ukazatele na objekt.
Pokud třída má nebo dědí virtuální funkce, inicializuje ukazatele virtuální funkce objektu.Virtuální funkce ukazatelů ukazuje na tabulku virtuální funkce třídy umožňující správnou vazbu volání virtuální funkce na kód.
Je spuštěn libovolný kód v těle jeho funkce.
Následující příklad zobrazuje pořadí, ve kterém jsou volány základní třídy a konstruktory členů v konstruktoru pro odvozenou třídu.Nejprve se volá základní konstruktor, poté se inicializují členy základní třídy v pořadí, v jakém se zobrazují v deklaraci třídy, a poté je volán odvozený konstruktor.
#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;
}
Zde je výstup:
Contained1 constructor.
Contained2 constructor.
BaseContainer constructor.
Contained3 constructor.
DerivedContainer constructor.
Pokud konstruktor vyvolá výjimku, pořadí zničení je obrácené pořadí konstrukce:
Kód v těle funkce konstruktoru je oddělen.
Základní třída a členské objekty jsou zničeny v obráceném pořadí deklarace.
Pokud je konstruktor bez delegování, jsou zničeny všechny úplně vytvořené objekty a členy.Vzhledem k tomu, že objekt samotný není úplně vytvořen, není spuštěn destruktor.
Explicitní konstruktory
Pomocí explicit klíčové slovo z konstruktoru, může zabránit převody implicitní typu, pokud konstruktoru má pouze jeden parametr nebo máte všechny parametry s výjimkou jednoho výchozí hodnotu.Další informace naleznete v tématu Konstruktory (C++).
Výchozí konstruktory
Výchozí konstruktory – to znamená konstruktory, které nemají parametry, se řídí trochu odlišnými pravidly.
Nejsou-li deklarovány žádné konstruktory ve třídě, kompilátor poskytuje výchozí konstruktor:
class Box {
int m_width;
int m_length;
int m_height;
};
int main(){
Box box1{};
Box box2;
}
Když voláte výchozí konstruktor a zkusíte použít závorky, bude vytvořeno upozornění:
class myclass{};
int main(){
myclass mc(); // warning C4930: prototyped function not called (was a variable definition intended?)
}
Toto je příklad problému Most Vexing Parse.Protože příklad výrazu může být interpretován jako deklarace funkce nebo jako vyvolání výchozího konstruktoru a protože deklarace analyzátoru jazyka C++ mají přednost před ostatními možnostmi, výraz je považován za deklaraci funkce.Další informace naleznete v tématu nejvíce Vexing analyzovat.
Nejsou-li deklarovány žádné nevýchozí konstruktory, kompilátor neposkytuje výchozí konstruktor:
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
}
Pokud třída nemá žádný výchozí konstruktor, pole objektů této třídy nemůže být vytvořeno pomocí syntaxe samostatných hranatých závorek.Například s ohledem na předchozí blok kódu nemůže být pole oken deklarováno takto:
Box boxes[3]; // compiler error C2512: no appropriate default constructor available
Můžete však použít sadu inicializačních seznamů pro inicializaci pole oken:
Box boxes[3]{ { 1, 2, 3 }, { 4, 5, 6 }, { 7, 8, 9 } };
Kopírování a přesun konstruktorů
Objekt kopie konstruktoru používá odkaz na objekt, chcete-li jeho kopii.Pokud nezadáte konstruktor kopie pro třídu, kompilátor vytvoří výchozí, i když byla deklarována operace kopírování, přesunutí nebo destruktor.Další informace naleznete v tématu Pravidla deklarace konstruktorů.
Objekt Přesunout konstruktor umožňuje přenos přidělené paměti z jednoho objektu do jiného.Další informace naleznete v tématu Postupy: Zápis konstruktoru přesunu.
Explicitně přednastavené a výchozí konstruktory
Explicitně výchozí konstruktory kopie, výchozí konstruktory, operátory přiřazení kopírování a destruktory, ale explicitní výchozí konstruktory přesunout a operátory přiřazení přesunout není podporován.(Tato podpora se nenachází v aplikaci Visual Studio 2015.) Explicitně lze odstranit všechny speciální funkce.Další informace naleznete v tématu Explicitně přednastavené a odstraněné funkce.
Konstruktory v odvozených třídách
Konstruktor odvozené třídy vždy volá konstruktor základní třídy, aby se před provedením jakékoli další práce mohl spolehnout na zcela konstruované základní třídy.Konstruktory základní třídy jsou volány v pořadí podle odvození – například pokud ClassA je odvozeno od ClassB, které je odvozeno od ClassC, je nejprve volán konstruktor ClassC, poté konstruktor ClassB a nakonec konstruktor ClassA.
Pokud základní třída nemá výchozí konstruktor, musíte zadat parametry konstruktoru základní třídy v konstruktoru odvozené třídy:
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);
}
Konstruktory pro třídy, které mají vícenásobnou dědičnost
Pokud je třída odvozena z více základních tříd, konstruktory základní třídy jsou vyvolány v pořadí, ve kterém jsou uvedeny v deklaraci odvozené třídy:
#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;
}
Můžete očekávat následující výstup:
BaseClass1 constructor.
BaseClass2 constructor.
BaseClass3 constructor.
DerivedClass constructor.
Virtuální funkce v konstruktorech
Doporučujeme vám, abyste byli při volání virtuálních funkcí v konstruktorech opatrní.Vzhledem k tomu, že konstruktor základní třídy je vždy vyvolán před konstruktorem odvozené třídy, funkce, která je volána v konstruktoru základny, je verze základní třídy, nikoli verze odvozené třídy.V následujícím příkladu tvorbu DerivedClass způsobí, že BaseClass provádění print_it() provést před DerivedClass konstruktor způsobí, že DerivedClass provádění print_it() ke spuštění:
#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;
}
Zde je výstup:
BaseClass print_it
Derived Class print_it
Konstruktory a složené třídy
Třídy, které obsahují členy typu třídy jsou známé jako složený třídy.Při vytvoření člena typu třídy pro složenou třídu je konstruktor volán před vlastním konstruktorem třídy.Pokud obsažená třída nemá výchozí konstruktor, musíte použít seznam inicializace v konstruktoru složené třídy.V dříve StorageBox příklad, je-li změnit typ m_label členské proměnné do nového Label třídy, musí volat konstruktor základní třídy a inicializovat m_label proměnné v StorageBox konstruktor:
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"});
}
Delegování konstruktorů
Objekt delegování konstruktoru volá jiný konstruktor ve stejné třídě, chcete-li provést některé úkoly inicializace.V následujícím příkladu má odvozená třída tři konstruktory – druhý konstruktor deleguje na první a třetí konstruktor deleguje na druhý:
#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);
}
Zde je výstup:
ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor constructor with 3 ints.
Objekt vytvořený pomocí konstruktorů je plně inicializován ihned po dokončení jakéhokoli konstruktoru.DerivedContainer(int int1) proběhne úspěšně, ale DerivedContainer(int int1, int int2) selže a destruktoru je volána.
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){
}
}
Výstup:
ConstructorDestructor constructor with 1 int.
ConstructorDestructor constructor with 2 ints.
ConstructorDestructor destructor.
Další informace naleznete v tématu Jednotná inicializace a delegování konstruktorů.