Предположим, у меня есть следующее:
#include <memory>
struct A { int x; };
class B {
B(int x, std::unique_ptr<A> a);
};
class C : public B {
C(std::unique_ptr<A> a) : B(a->x, std::move(a)) {}
};
Если я правильно понимаю правила C ++ относительно «неопределенного порядка параметров функций», этот код небезопасен. Если второй аргумент B
конструктор сначала создается с помощью конструктора перемещения, затем a
теперь содержит nullptr
и выражение a->x
вызовет неопределенное поведение (вероятно, segfault). Если первый аргумент создается первым, то все будет работать так, как задумано.
Если бы это был обычный вызов функции, мы могли бы просто создать временный:
auto x = a->x
B b{x, std::move(a)};
Но в списке инициализации класса у нас нет свободы для создания временных переменных.
Предположим, я не могу изменить B
Есть ли какой-нибудь возможный способ для достижения вышеизложенного? А именно разыменование и перемещение unique_ptr
в том же выражении вызова функции без создания временного?
Что делать, если вы могли бы изменить B
конструктор, но не добавлять новые методы, такие как setX(int)
? Это поможет?
Спасибо
Используйте инициализацию списка для создания B
, Элементы гарантированно будут оцениваться слева направо.
C(std::unique_ptr<A> a) : B{a->x, std::move(a)} {}
// ^ ^ - braces
От §8.5.4 / 4 [dcl.init.list]
В пределах инициализатора-лист из приготовился-INIT-лист, инициализатора-статьи, включая любые, которые являются результатом расширений пакета (14.5.3), оцениваются в порядке, в котором они появляются. То есть каждое значение вычисления и побочный эффект связаны с данным инициализатор-раздел последовательность перед каждым вычислением значения и побочным эффектом, связанным с любым инициализатор-раздел который следует за ним в списке через запятую инициализатора-лист.
В качестве альтернативы ответу Преториана вы можете использовать делегат конструктора:
class C : public B {
public:
C(std::unique_ptr<A> a) :
C(a->x, std::move(a)) // this move doesn't nullify a.
{}
private:
C(int x, std::unique_ptr<A>&& a) :
B(x, std::move(a)) // this one does, but we already have copied x
{}
};
Предложение Преториана об использовании инициализации списка, кажется, работает, но у него есть несколько проблем:
B
случайно забыть использовать {}
вместо ()
, Дизайнеры B
Интерфейс наложил эту потенциальную ошибку на нас.Если бы мы могли изменить B, то, возможно, лучшее решение для конструкторов — всегда передавать unique_ptr по ссылке rvalue, а не по значению.
struct A { int x; };
class B {
B(std::unique_ptr<A>&& a, int x) : _x(x), _a(std::move(a)) {}
};
Теперь мы можем безопасно использовать std :: move ().
B b(std::move(a), a->x);
B b{std::move(a), a->x};