вступление

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

Тема разных типов конструкторов очень важна для понимания, особенно когда например надо унаследоваться от отрытых профессиональных библиотек типа 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  ссылки.

Далее значительный вынос мозга связан с наследованием классов и с  развитием их конструкторов и т.д., см. наследование конструкторов.