Можно ли выполнить двухэтапную инициализацию неподвижного объекта в списке инициализаторов членов с помощью С++ 17?
Вот устаревший API, с которым я работаю (и да, я знаю, что это плохо, но я не могу его изменить)
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
};
И вот что я пытаюсь написать:
class C {
public:
C();
private:
A m_a;
B m_b;
};
C::C()
: m_a{ /* two step initialization of A*/}
, m_b(m_a)
{}
Обычный трюк с использованием лямбды с немедленной инициализацией не работает:
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)
{}
Также не используется тело конструктора:
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);
}
И не делает второй шаг двухэтапной инициализации в теле конструктора:
C::C()
: m_a(0xdead_beef)
, m_b(m_a) // m_a isn’t fully initialized here
{
m_a.init();
}
В настоящее время я использую unique_ptr
вместо m_b
, но я спрашивал себя, есть ли лучшее решение.
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)
{}
Я думаю, что правила гарантированного исключения ходов были улучшены в C++20, но я все еще использую C++17.
@Элджей: operator""_beef
не показано :)
«Я думаю, что правила гарантированного исключения перемещения были улучшены в C++20» — исключения могут происходить только в том случае, если перемещение/копирование разрешено в первой точке.
Альтернативой может быть написание оболочки вокруг A
с помощью конструктора, который вызывает init
. WrapperA::WrapperA(int n1, int n2) : m_a(n1) {m_a.init(n2);}
и class C {WrapperA m_wa; B m_b; C() : m_wa(0xdead, 42)}, m_b(m_wa.m_a) {}
.
Вы все еще можете злоупотреблять оператором запятой:
C::C() : m_a{ 0xdeadbeef },
m_b((m_a.init(42), m_a))
{}
Спасибо! Я даже не ожидал, что это возможно. И то, что я сделал, еще более проклято, но на самом деле init()
возвращал логическое значение, и если это логическое значение было правдой, код, который я очищал, выдавал исключение. Поэтому я просто использовал троицу: `m_b(m_a.init(42) ? m_a : throw "error").
0xdead_beef
...? Это не должно компилироваться.