【问题标题】:Why is the move constructor neither declared nor deleted with clang?为什么移动构造函数既没有用clang声明也没有删除?
【发布时间】:2014-01-03 17:03:04
【问题描述】:

考虑以下类。

struct with_copy {
    with_copy() = default;
    with_copy(with_copy const&) {}
    with_copy& operator=(with_copy const&) { return *this; }
};

struct foo {
    with_copy c;
    std::unique_ptr<int> p;
};
  • with_copy 有复制构造函数吗?是的。它是明确定义的。
  • with_copy 有移动构造函数吗?不会。显式复制构造函数会阻止生成它。
  • with_copy 是否有已删除的移动构造函数?不,没有移动构造函数与删除构造函数不同。已删除的移动构造函数会尝试移动格式错误而不是退化为副本。
  • with_copy 是否可复制?是的。它的复制构造函数用于复制。
  • with_copy 可以移动吗?是的。它的复制构造函数用于移动。

...现在是棘手的问题。

  • foo 有复制构造函数吗?是的。它有一个已删除的,因为它的默认定义由于调用 unique_ptr 的已删除复制构造函数而格式错误。
  • foo 有移动构造函数吗? GCC 说是,clang 说不是。
  • foo 是否有已删除的移动构造函数? GCC 和 clang 都说不。
  • foo 是否可复制?不,它的复制构造函数被删除了。
  • foo 可以移动吗? GCC 说是,clang 说不是。

(当考虑赋值而不是构造时,行为类似。)

据我所知,GCC 是正确的。 foo 应该有一个移动构造函数,它对每个成员执行移动,在 with_copy 的情况下,它退化为一个副本。 Clang 的行为似乎很荒谬:我有一个包含两个可移动成员的聚合体,但我的聚合体是一块不可移动的砖。

谁是对的?

【问题讨论】:

  • 从技术上讲,我认为 Standardese 是 with_copy 复制构造函数是用户提供的
  • 在 n3485 中,[class.copy]/11 说如果“对于移动构造函数,非静态数据成员或直接或虚拟基类的类型不具有一个移动构造函数,不能轻易复制。”而且with_copy 是不可轻易复制的。
  • @DyP 没有编译器生成删除的移动构造函数:S(而且这些规则很荒谬)
  • 因为未声明隐式删除的移动构造函数,/9.
  • 顺便说一句,这在 C++1y 中发生了变化。据我所知,github repo 中的最新草案说foo 有一个移动构造函数。

标签: c++ c++11 user-defined-functions move-semantics implicit-declaration


【解决方案1】:

C++11,或者更确切地说是 n3485,[class.copy]/9:

如果类X的定义没有显式声明移动构造函数,则会隐式声明 默认当且仅当

  • X 没有用户声明的复制构造函数,
  • X 没有用户声明的复制赋值运算符,
  • X 没有用户声明的移动赋值运算符,
  • X 没有用户声明的析构函数,并且
  • 移动构造函数不会被隐式定义为已删除。

和/11:

隐式声明的复制/移动构造函数是其类的inline public 成员。默认副本/ 如果 X 具有以下属性,则 X 类的移动构造函数被定义为已删除 (8.4.3):

  • [...]
  • 对于复制构造函数,右值引用类型的非静态数据成员,或
  • 对于移动构造函数,非静态数据成员或直接或虚拟基类,其类型为 没有移动构造函数,也不能轻易复制。

由于with_copy不可 简单可复制的,foo 将有 no 移动构造函数(它将被定义为已删除,因此它不会被隐式声明)。


C++1y,或者更确切地说是 github repo 从 2013-11-12 提交 e31867c0;合并DR1402:

/9:

如果类X的定义没有明确声明移动 构造函数,一个将被隐式声明为默认当且仅 如果

  • X 没有用户声明的复制构造函数,
  • X 没有用户声明的复制赋值运算符,
  • X 没有用户声明的移动赋值运算符,并且
  • X 没有用户声明的析构函数。

和/11:

隐式声明的复制/移动构造函数是inline public 其类的成员。类X的默认复制/移动构造函数 如果X 具有以下属性,则定义为已删除 (8.4.3):

  • [...]
  • 对于复制构造函数,右值引用类型的非静态数据成员。

定义为已删除的默认移动构造函数被忽略 重载决议(13.3、13.4)。

这里,foo 将有一个移动构造函数。

【讨论】:

  • std::is_move_constructible&lt;T&gt; 被定义为 is_constructible&lt;T, T&amp;&amp;&gt; 反过来只是检查 T(declval&lt;T&gt;()) 是否格式正确。因此,在std::is_move_constructible 的意义上,没有声明的移动 ctor(或在重载决议期间被忽略的移动 ctor)的类型仍然是“可移动的”。
  • CWG 1402 是(恕我直言)对 C++11 最重要的错误修复。 Tip-of-trunk clang 实现了它,即使在 -std=c++11 模式下也是如此。
【解决方案2】:

我不太确定您测试了什么,但它foo 肯定既可移动分配又可移动构造。诚然,这并没有说明移动构造函数或移动赋值是可访问的,只是来自右值的构造或赋值有效。 clang(clang version 3.5(trunk 196718))和gcc(gcc version 4.9.0 20131031(experimental)(GCC))都同意这个评估。这是我尝试过的完整来源:

#include <iostream>
#include <type_traits>
#include <memory>

struct with_copy {
    with_copy() = default;
    with_copy(with_copy const&) {}
    with_copy& operator=(with_copy const&) { return *this; }
};

struct foo {
    with_copy c;
    std::unique_ptr<int> p;
};

int main()
{
    std::cout << "move constructible: "
              << std::is_move_constructible<foo>::value << '\n';
    std::cout << "move assignable: "
              << std::is_move_assignable<foo>::value << '\n';
    foo f0;
    foo f1 = std::move(f0);
    f0 = std::move(f1);
}

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2015-04-17
    • 1970-01-01
    • 1970-01-01
    • 2012-08-13
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多