使用常量数据成员或引用成员移动类的ctor

时间:2012-11-26 13:13:15

标签: c++ constructor c++11 move-semantics

我在解释何时以及是否调用移动构造函数或移动赋值运算符时遇到了一些问题,特别是在具有常量数据成员的类的上下文中。 考虑班级

template<typename T> class A {
  const*T const P ;   // constant data member
  explicit A(const*T p) : P(p) { std::cerr<<" ctor: P="<<P<<'\n'; }
  void test() const { std::cerr" test: P="<<P<<'\n'; }
  // move and copy constructors and assignment operators here
};

和测试程序

class B {
  int X[100];
  A<B> get_a() const { return A<B>(this); }
};

int main() {
  B b;
  A<B> a = b.get_a();   // which operator/ctor is used for '=' here?
  a.test();
}

然后编译的结果会有所不同,具体取决于为类A<>中的移动构造函数和移动赋值运算符提供的定义,还有编译器。

1 在类A<>中没有任何进一步声明(如上所述),g ++(4.7.0)和icpc(13.0.1)编译正常(使用选项{{1} })并产生预期的输出

-std=c++11

2 如果我宣布

ctor: P=0x7fffffffd480
test: P=0x7fffffffd480

(鉴于必须初始化初始化的常量数据成员,这看起来很合理),但是不提供任何进一步的ctor,编译使用g ++失败,但是对于icpc是可以的。如果另外我定义了(或两者)

A&A::operator=(A&&) = delete;
A&A::operator=(const A&) = delete;
两个编译器都很高兴。但是,g ++对组合

不满意
A::A(A&&) = default;
A::A(const A&) = default;

虽然icpc很高兴。

3 如果我使用与 2 相同的游戏,则A::A(A&&) = delete; A::A(const A&) = default; 替换为

A::A(A&&) = default;

(和A::A(A&&a) : P(a.P) { std::cerr<<" move ctor: P="<<P<<'\n'; } // never called? 的等价物),结果完全相同,特别是没有输出从这些显式移动和复制ctors生成。

A::A(const A&)中哪个运算符用于=? (为什么在最后一次测试中没有产生输出?)

为什么在这里允许此操作,因为main()有一个常量数据成员(如果我用[{1}}替换成员A<>,结果是相同的)?

最后,如果g ++和icpc有不同的行为,哪些是正确的?

1 个答案:

答案 0 :(得分:2)

A<B> a = b.get_a();

不是赋值,而是从右值初始化a。如果

,此语法应在C ++ 0x下失败
  1. 删除了移动构造函数,
  2. 移动构造函数声明为explicit
  3. 删除了复制构造函数,并且没有定义移动构造函数,
  4. 未定义移动构造函数,同时定义或删除移动分配。
  5. 宣布或删除副本分配操作符不应有任何影响。

    更正:与复制构造函数不同(即使提供了用户定义的复制赋值运算符,也会合成),如果定义了用户定义的移动赋值,则编译器不会合成移动构造函数。因此,上面的清单应该修改为4(我现在已经做过)。

    因此,在我看来,

      问题中[1]中的
    • ,两个编译器都表现正确,
    • 在[2] / 1中,gcc行为正确(移动赋值可防止生成移动构造函数),icpc错误,
    • 在[2] / 2中,两个编译器都是正确的,
    • 在[2] / 3中,gcc是正确的,因为显式删除了移动构造函数; icpc错了,
    • [3]对我来说是一个谜。你确定你是对的吗?