Вообще говоря конструктор это обычная функция, вот только вызывается она в определенных (иногда трудных для понимания местах кода) и иногда не явно (по умолчанию) при создании, копировании, присваивании об'ектов. Синтаксис таких сентенций многообразен и это вводит часто в заблуждение.
Тема разных типов конструкторов очень важна для понимания, особенно когда например надо унаследоваться от отрытых профессиональных библиотек типа Qt Sources, дабы сделать свое развитие (форк). И тут без понимания зачем в исходниках реализована куча конструкторов и как с ними себя вести - придется разобраться по взрослому.
Конструкторы по умолчанию
Первое,что надо уяснить - если в классе не прописаны конструкторы это не значит, что их нет. На самом деле они есть: конструктор по умолчанию, конструктор копирования и возможно еще какие-то, см. сводная таблица по неявным конструкторам.
Начинаем погружение как обычно с самого простого примера - пустой класс А:
#include <QtCore/QCoreApplication>
class A
{
};
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
A clA;
return a.exec();
}
Как мы будем разбираться в конструкторах? Правильно будем логгировать их, так как это по сути обычные функции:
Вот первый пример, где можно догадаться по логу, что при выходе из функции foo происходят вызовы деструкторов классов, но вызовы конструктов скрыты, так как реализованы компилятором по умолчанию (не явно):
class A
{
public:
~A()
{
qDebug() << "~A " << this;
}
};
void foo() // при выходе из функции все об,екты созданные в ней на стеке //будут гарантированно уничтожены компилятором, поэтому она нам и нужна
{
A(); // надо понимать , что тут не ошибка и реально создается объект класса А
A clA; // обычный конструктор по умолчанию
A clA2 = A();
A clB(clA); // конструктор по умолчанию копирования
qDebug() << "<- foo()";
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "--------main()---------";
qDebug() << "-> foo()";
foo();
qDebug() << "return from foo()";
return a.exec();
}
Вывод консоли: --------main()---------
-> foo()
<- foo()
~A 0xd3f70b
~A 0xd3f70a
return from foo()
Видно по выводу консоли, что деструкторы есть, а конструкторов как бы нет. Точнее они реализованы по умолчанию компилятором для класса А. Реализуем их явно в коде программы и все станет понятно:
class A
{
public:
A() // конструктор по умолчанию обычный
{
qDebug() << "A() this:" << this;
}
A(const A& in) // конструктор по умолчанию копирования
{
qDebug() << "A(const A& in) " << " this:" << this;
}
~A()
{
qDebug() << "~A " << this;
}
};
void foo()
{
A clA;
A clB(clA);
qDebug() << "<- foo()";
}
int main(int argc, char *argv[])
{
QCoreApplication a(argc, argv);
qDebug() << "--------main()---------";
qDebug() << "-> foo()";
foo();
qDebug() << "return from foo()";
return a.exec();
}
Вывод консоли:
--------main()---------
-> foo()
A() this: 0x3ef796
A(const A& in) this: 0x3ef797
<- foo()
~A 0x3ef797
~A 0x3ef796
return from foo()
Можно например ещё посмотреть оператор присваивания operator =. Это не конструктор, но тем не менее очень близкая тема.
Чтобы как-то упростить понимание взаимосвязи конструкторов (а они взаимосвязаны) придумали правило 3, позднее с выходом стандарта С11 придумали правило 5.
Особенное место в конструкторах занимает конструктор перемещения и rvalue ссылки.
Далее значительный вынос мозга связан с наследованием классов и с развитием их конструкторов и т.д., см. наследование конструкторов.