Mat*_*nte 49 c++ initialization move-semantics unspecified-behavior c++11
假设我有以下内容:
#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)) {}
};
Run Code Online (Sandbox Code Playgroud)
如果我正确理解有关"未指定函数参数顺序"的C++规则,则此代码不安全.如果B构造函数的第二个参数首先使用移动构造函数构造,那么a现在包含一个nullptr表达式a->x将触发未定义的行为(可能是段错误).如果首先构造第一个参数,那么一切都将按预期工作.
如果这是一个正常的函数调用,我们可以创建一个临时函数:
auto x = a->x
B b{x, std::move(a)};
Run Code Online (Sandbox Code Playgroud)
但是在类初始化列表中,我们没有自由创建临时变量.
假设我无法改变B,有没有可能的方法来实现上述目标?即unique_ptr在不创建临时的情况下解除引用并移动相同的函数调用表达式?
如果您可以更改B构造函数但不添加新方法,该setX(int)怎么办?那会有帮助吗?
谢谢
Pra*_*ian 47
使用列表初始化来构造B.然后保证从左到右评估元素.
C(std::unique_ptr<A> a) : B{a->x, std::move(a)} {}
// ^ ^ - braces
Run Code Online (Sandbox Code Playgroud)
来自§8.5.4/ 4 [dcl.init.list]
内的初始列表一个的支撑-INIT列表中,初始化子句,从包扩展(14.5.3)导致,包括任何被以它们出现的顺序进行评价.也就是说,与给定初始化子句相关联的每个值计算和副作用在每个值计算和副作用之前与在初始化列表的逗号分隔列表中跟随它之后的任何初始化子句相关联.
Jar*_*d42 32
作为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
{}
};
Run Code Online (Sandbox Code Playgroud)
Mat*_*nte 10
Praetorian建议使用列表初始化似乎有效,但它有一些问题:
B意外忘记使用{}而不是().B界面的设计者给我们带来了这个潜在的错误.如果我们可以改变B,那么构造函数的一个更好的解决方案是总是通过rvalue引用而不是value来传递unique_ptr.
struct A { int x; };
class B {
B(std::unique_ptr<A>&& a, int x) : _x(x), _a(std::move(a)) {}
};
Run Code Online (Sandbox Code Playgroud)
现在我们可以安全地使用std :: move().
B b(std::move(a), a->x);
B b{std::move(a), a->x};
Run Code Online (Sandbox Code Playgroud)