6

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

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();
}

那么编译的结果会有所不同,具体取决于为 class 中的 move 构造函数和 move 赋值运算符提供的定义A<>,但也取决于编译器。

1在类中没有任何进一步声明A<>(如上),g++ (4.7.0)和 icpc (13.0.1)都可以正常编译(使用 option -std=c++11)并产生预期的输出

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

2如果我声明

A&A::operator=(A&&) = delete;
A&A::operator=(const A&) = delete;

(鉴于必须初始化初始化列表的常量数据成员,这似乎是明智的),但不提供任何进一步的 ctor,g++ 编译失败,但 icpc 没问题。如果另外我定义了一个(或两个)

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

两个编译器都很高兴。但是,g++ 对这种组合并不满意

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

而icpc很高兴。

3如果我玩与2中相同的游戏,除了A::A(A&&) = default;替换为

A::A(A&&a) : P(a.P) { std::cerr<<" move ctor: P="<<P<<'\n'; } // never called?

(和 等效A::A(const A&)),结果完全相同,特别是这些显式移动和复制 ctor 不会生成任何输出。

那么哪个运算符用于=in main()?(为什么最后一次测试没有输出?)

A<>既然有一个常量数据成员(如果我用 替换成员,结果是相同的const*T const P;),为什么这里完全允许这个操作const T&R

最后,在 g++ 和 icpc 的不同行为的情况下,如果有的话,哪个是正确的?

4

1 回答 1

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

不是赋值,而是从右值初始化a。此语法在 C++0x 下应该失败,如果

  1. 移动构造函数被删除,
  2. 声明了移动构造函数explicit
  3. 复制构造函数被删除并且没有定义移动构造函数,
  4. 没有定义移动构造函数,同时定义或删除了移动赋值。

复制赋值运算符的声明或删除不应有任何影响。

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

因此,在我看来,

  • 在问题的 [1] 中,两个编译器的行为都正确,
  • 在 [2]/1 中,gcc 行为正确(移动赋值阻止生成移动构造函数),icpc 错误,
  • 在 [2]/2 中,两个编译器都是正确的,
  • 在 [2]/3 中,gcc 是正确的,因为移动构造函数被显式删除;icpc错了,
  • [3] 对我来说是个谜。你确定你是对的吗?
于 2012-11-26T13:26:55.000 回答