Home > database >  Is it possible to do a two-step initialization of a non-movable object in the member initializer lis
Is it possible to do a two-step initialization of a non-movable object in the member initializer lis

Time:11-26

Is it possible to do a two-step initialization of a non-movable object in the member initializer list using C 17?


Here is the legacy API I’m working with (and yes I know it’s bad but I can’t change it)

class A {
public:
    A(int some_param);

    // no default, move or copy constructor nor assignment
    A() = delete
    A(const& A) = delete;
    A(const&&) = delete;      // it shouldn’t be deleted, but I’m not able to modify that class
    A& operator= (const& A) = delete;
    A& operator= (const&&) = delete;

    // and it uses two phase construction
    void init(int some_other_argument);

    // more stuff
};

class B {
public:
    // no default constructor, and must be created with a fully-initialized A
    B() = delete;
    B(const& A);

    // more stuff
};

And here is what I am trying to write:

class C {
public:
    C();
private:
    A m_a;
    B m_b;
};

C::C()
    : m_a{ /* two step initialization of A*/}
    , m_b(m_a)
{}

The usual trick of using a immediately initialize lambda doesn’t work:

C::C()
    : m_a{[] {
        auto a = A(0xdead_beef);
        a.init(42);
        return a; // doesn’t compile because A isn’t movable
    }()}
    , m_b(m_a)
{}

Nor does using the body of the constructor:

C::C()
    // m_a and m_b will be default-initialized here, but their default initializer are deleted
{
    m_a = A(0xdead_beef);
    m_a.init();
    m_b = B(m_a);
}

And nor doing the second step of the two step initialization in the body of the constructor:

C::C()
    : m_a(0xdead_beef)
    , m_b(m_a) // m_a isn’t fully initialized here
{
    m_a.init();
}

Currently I’m using a unique_ptr for m_b, but I was asking myself if there was a better solution.

class C {
public:
    C();
private:
    std::unique_ptr<A> m_a; // guaranted to be initialized in the constructor, no need to check before dereferencing
    B m_b;
};

C::C()
    : m_a{[] {
        auto a = new A(0xdead_beef);
        a->init(42);
        return a;
    }()}
    , m_b(*m_a)
{}

I think the rules of guaranteed move elision were improved in C 20, but I’m still using C 17.

CodePudding user response:

You might still abuse of comma operator:

C::C() : m_a{ 0xdeadbeef },
         m_b((m_a.init(42), m_a))
{}
  • Related