С++. Классы. Виртуальное наследование. Проектирование классов

Имеется родительский класс A, в котором объединены различные функции и другие классы, как члены данного класса (в примере это поинтер класса D). Далее я создаю класс A_Object, данный класс будут наследовать другие классы, чтобы они могли использовать функции класса A. Так как мне нужно, чтобы класс A создавался единожды (соотвественно и не повторялось создание класса D), я использую виртуальное наследование. Любые другие классы, например, B и C, уже будут наследовать класс A_Object, таким образом я единожды создаю класс A и использую его функции через наследование класса A_Object. Построение классов начинается с класса A, но почему-то вызывается его пустой конструктор (хотя насколько я понял, должен вызываться конкретно конструктор с аргументом, т.к. при создании класса B я передаю этот аргумент). Как итог программа завершает работу неправильно. Скорее всего, я изначально неправильно проектирую построение классов. Поэтому прошу помочь либо с корректировкой моего примера либо с проектировкой нового.

/*
result:

    init B
    A()
    A_Object(std::string auth): auth-token
    D(std::string auth):
    B(std::string auth): auth-token
    D::print():
    init C
    A()
    A_Object()
    C()
    D::print():
    test.exe (процесс 9168) завершил работу с кодом -1073741819.
*/

class D
{
    std::string m_auth;

public:
    D(std::string auth) :
        m_auth(auth)
    {
        std::cout << "D(std::string auth): " << auth << std::endl;
    }

    void print()
    {
        std::cout << "D::print(): " << m_auth << std::endl;
    }
};

class A
{
    D* m_class_d;
    std::string m_auth;

public:
    A() :
        m_class_d(nullptr)
    {
        std::cout << "A()" << std::endl;
    }

    A(std::string auth) :
        m_class_d(nullptr),
        m_auth(auth)
    {
        std::cout << "A(std::string auth): " << auth << std::endl;
    }

    ~A()
    {
        delete m_class_d;
    }

    void create()
    {
        m_class_d = new D(m_auth);
    }

    D* get_class_d()
    {
        return m_class_d;
    }

    std::string get_auth()
    {
        return m_auth;
    }
};

class A_Object : virtual public A
{
public: 
    A_Object()
    {
        std::cout << "A_Object()" << std::endl;
    }

    A_Object(std::string auth) :
        A(auth)
    {
        std::cout << "A_Object(std::string auth): " << auth << std::endl;
        create();
    }
};

class B : public A_Object
{
public:
    B(std::string auth) :
        A_Object(auth)
    {
        std::cout << "B(std::string auth): " << auth << std::endl;
    }
};

class C : public A_Object
{
public:
    C()
    {
        std::cout << "C()" << std::endl;
    }
};

int main()
{
    std::cout << "init B" << std::endl;
    B b("auth-token");
    b.get_class_d()->print();

    std::cout << "init C" << std::endl;
    C c;
    c.get_class_d()->print();

    return 0;
}```

Ответы (1 шт):

Автор решения: AR Hovsepyan

Чтобы было понятней, давайте упростим код:

class A
{
public:
    A()        
    {
        std::cout << "A()" << std::endl;
    }
    A(const std::string& auth) 
    {
        std::cout << "A(std::string auth): "<< std::endl;
    }   
};
class A_Object : virtual public A
{
public:
    A_Object() 
    {
        std::cout << "A_Object()" << std::endl;
    }
    A_Object(const std::string& auth) :
        A(auth)
    {
        std::cout << "A_Object(std::string auth): " << auth << std::endl;        
    }
};

class B : public A_Object
{
public:
    B(const std::string& auth) :
        A_Object(auth)
    {
        std::cout << "B(std::string auth): " << auth << std::endl;
    }
};

Для A_Object a будет вызываться конструктор по умолчанию базового класса, до конструирования объекта . Результат:

A()
A_Object()
    

Для B b("auth-token") Будет вызываться в первую очередь конструктор базового класса, на чем строится объект b, а значит конструктор A(). Что выводит на экран:

A()

Так как в списке инициализатора конструктора целевого объекта нет этого конструктора, а есть только конструктор A_Object, который в свою очередь вызвал бы конструктор своего базового класса, если бы он не был виртуальным, а с учетом виртуальности, у него уже есть базовый объект, который создан конструктором класса B а значит будет выведен только:

A_Object(std::string auth): auth-token
B(std::string auth): auth-token 

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

B::B(const std::string& auth) : A(auth),
    A_Object(auth) {... }

Тогда и получите результат с вызовом конструктора виртуального базового класса А с аргументом.

→ Ссылка