Move semantics and function order evaluation
假设我有以下内容:
1 2 3 4 5 6 7 8 9 10 | #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 ++规则,则此代码不安全。 如果
如果这是正常的函数调用,我们可以创建一个临时的:
1 2 | auto x = a->x B b{x, std::move(a)}; |
但是在类初始化列表中,我们没有创建临时变量的自由。
假设我不能更改
如果可以更改
谢谢
使用列表初始化来构造
1 2 | C(std::unique_ptr<A> a) : B{a->x, std::move(a)} {} // ^ ^ - braces |
从§8.5.4/ 4 [dcl.init.list]
Within the initializer-list of a braced-init-list, the initializer-clauses, including any that result from pack expansions (14.5.3), are evaluated in the order in which they appear. That is, every value computation and side effect associated with a given initializer-clause is sequenced before every value computation and side effect associated with any initializer-clause that follows it in the comma-separated list of the initializer-list.
作为Praetorian答案的替代方法,可以使用构造函数委托:
1 2 3 4 5 6 7 8 9 10 11 | 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关于使用列表初始化的建议似乎可行,但存在一些问题:
如果我们可以更改B,那么对于构造函数来说,更好的解决方案是始终通过rvalue引用而不是按值传递unique_ptr。
1 2 3 4 5 | struct A { int x; }; class B { B(std::unique_ptr<A>&& a, int x) : _x(x), _a(std::move(a)) {} }; |
现在我们可以安全地使用std :: move()了。
1 2 | B b(std::move(a), a->x); B b{std::move(a), a->x}; |