Search code examples
c++qtconstructordelegates

Calling delegated constructor while creating new object with `this`


Let's say I have a class that initializes several member pointers by passing itself to them (reduced to only A). It is a common practice in Qt to pass the parent widget.

class B : public Base{
public:
    B() :
        my_a(new A(this))
    {  ...  }
    A* my_a;
};

But to make it testable I'd like to set those pointers from the outside, preferably passing in the constructor without duplicating everything between the brackets.
That might look like this:

class B : public Base{
    public:
    B() :
        B(new A(this))
    {   }
    B(A* a) :
        my_a(a)
    {  ...  }
    
    A* my_a;
};

From B's default constructor we call the specialized version of it. But the problem is that it cause an undefined behavior - sometimes crashing but sometimes produce 'impossible' output. I was able to narrow it down till the : B(new A(this)) part - calling a delegate constructor while creating a new object with this.

The question: What cause the UB in that case? Is it possible to use delegate constructors while initializing new object with the current instance?

If nothing works I would create 2 constructors without delegating and an init method to remove code duplication.

Ps. consider Base as QWidget, while A and B being custom widgets.

Edit: Here's a short version of it that can be compiled but different runs can produce different output:

#include <iostream>
#include <string>

class Base 
{
    public:
    int get_int() const {return my_int;};
    
    int my_int = 5;
};

class A
{
public:
    A(Base* base = nullptr)
    {
        if(base != nullptr) {
            my_i = base->get_int();
        } else {
            my_i = 42;
        }
    }
        
    int my_i;
};

class B : public Base{
    public:
    B() :
        B(new A(this))
    {
        
    }
    B(A* a) :
        my_a(a)
    {}
    
    
    A* my_a;
};

int main ()
{
    B b;
    std::cout << b.my_a->my_i;
    return 0;
}

Solution

  • In B() : B(new A(this)) the instance of A must be created as part of evaluating the arguments for the delegate constructor. That means it occurs before any initialization happens for this, including before the base class constructor being called. In this case my_i is not initialized yet when the A is constructed. You then have Undefined Behaviour from reading an uninitialied data member while calling get_int();.

    In B() : my_a(new A(this)) the instance of A is created in member initializer list, which means it is always created after the base class constructors have finished. In this case my_i is initialized before the A is constructed and get_int() works correctly in A's constructor.