移动语义和function顺序评估
假设我有以下几点:
#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
的构造函数的第二个参数是使用移动构造函数首先构造的,那么现在包含一个nullptr
,expression式a->x
将触发未定义的行为(可能是段错误)。 如果第一个参数是先构造的,那么一切都将按预期工作。
如果这是一个正常的函数调用,我们可以创build一个临时的:
auto x = a->x B b{x, std::move(a)};
但在类初始化列表中,我们没有创build临时variables的自由。
假设我不能改变B
,是否有任何可能的方法来完成上述? 即解除引用和移动一个unique_ptr
在同一个函数调用expression式而不创build临时?
如果你可以改变B
的构造函数,但不添加新的方法,如setX(int)
? 会有帮助吗?
谢谢
使用列表初始化来构造B
然后保证元素从左到右进行评估。
C(std::unique_ptr<A> a) : B{a->x, std::move(a)} {} // ^ ^ - braces
从§8.5.4/ 4 [dcl.init.list]
在braced-init-list的初始化程序 列表中 , 初始化程序子句 (包括从程序包扩展(14.5.3)产生的任何子程序子句 )按其出现顺序进行评估。 也就是说,与给定初始化子句相关联的每个值计算和副作用在与初始值设定项列表的逗号分隔列表中的任何初始化子句相关联的每个值计算和副作用之前被sorting。
作为Praetorian的答案的替代,你可以使用构造函数委托:
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 {} };
Praetorian的使用列表初始化的build议似乎工作,但它有几个问题:
- 如果unique_ptr参数是第一个,我们就不幸运了
- 对于
B
客户来说,它太容易忘记使用{}
而不是()
。B
界面的devise者已经给我们带来了这个潜在的bug。
如果我们可以改变B,那么对于构造函数来说,一个更好的解决scheme就是总是通过右值引用而不是按值传递unique_ptr。
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};