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))
{}