【问题标题】:move ctor of class with a constant data member or a reference member使用常量数据成员或引用成员移动类的ctor
【发布时间】:2012-11-13 23:22:17
【问题描述】:

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

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&lt;&gt; 类中的移动构造函数和移动赋值运算符提供的定义,但也取决于编译器。

1 在类A&lt;&gt;(如上)中没有任何进一步声明,g++(4.7.0)和icpc(13.0.1)都可以正常编译(使用选项-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&amp;&amp;) = default;被替换为

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

(等效于A::A(const A&amp;)),结果完全相同,特别是没有输出从这些显式移动和复制ctors中生成。

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

鉴于A&lt;&gt; 有一个常量数据成员(如果我将成员const*T const P; 替换为const T&amp;R,结果是相同的),为什么这里完全允许此操作?

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

【问题讨论】:

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


    【解决方案1】:
    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] 对我来说是个谜。你确定你是对的吗?

    【讨论】:

    • 这是否意味着 g++ 和 icpc 都在这里犯了几个错误?特别是,如果所有相关操作都应该写入标准错误,我仍然对缺少任何输出感到困惑。
    • 我很确定 [3]。你可以自己试试,代码很简单。顺便说一句,为什么析构函数的存在会改变游戏规则?
    • 因为,根据 Torsten Will 的一本书,析构函数的存在阻止了移动构造函数的自动生成。但是,由于它不会阻止复制构造函数的生成,因此我不确定这在实践中意味着什么。
    • [3] 可以用复制省略来解释。 a 对象是就地构造的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-03
    • 2013-01-08
    • 1970-01-01
    • 2012-12-13
    • 2021-09-19
    • 1970-01-01
    相关资源
    最近更新 更多