默认移动 constructor/assignment 并删除副本 constructor/assignment

Default move constructor/assignment and deleted copy constructor/assignment

按照标准,

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

— X does not have a user-declared copy constructor,

— X does not have a user-declared copy assignment operator,

— X does not have a user-declared move assignment operator, and

— X does not have a user-declared destructor.

下面编译失败

# include <utility>

class Foo
{
public:
  Foo() = default;
  Foo(Foo const &) = delete;
};

int main()
{
  Foo f;
  Foo g(std::move(f)); // compilation fails here
  return 0;
}

所以似乎删除的函数被认为是用户定义的,这是有道理的(这不是它的默认实现)。但是,在那种特殊情况下,如何删除副本 construtor/assignment 乱码默认移动 constructor/assignment?

我认为这个问题具有实际意义,因为手动生成和 esp。此类默认函数的维护很容易出错,而与此同时,class 等 std::unique_ptr 作为 class 成员的(正当)使用增加使得不可复制 class比以前更常见的野兽。

行为基本上可以用以下方式说明:

void foo(const int& i)
{
    std::cout << "copy";
}

void foo(int&& i)
{
    std::cout << "move";
}

当两个重载都存在时,为右值选择 int&& 重载,为左值选择 const int& 重载。如果删除 int&& 重载,即使是右值也会调用 const int& (因此,不是错误)。这基本上就是这种情况下发生的事情:

class Foo
{
public:
  Foo() = default;
  Foo(Foo const &) = delete;
};

重载决议只看到一个候选人,但由于它被明确删除,程序格式错误。

您引用的部分下方的非规范性注释阐明了这是正在发生的事情:

[ Note: When the move constructor is not implicitly declared or explicitly supplied, expressions that otherwise would have invoked the move constructor may instead invoke a copy constructor. — end note ]

如您所说,从 §12.8 开始

If the definition of a class X does not explicitly declare a move constructor, one will be implicitly declared as defaulted if and only if

  • X does not have a user-declared copy constructor,

  • [...]

注意用户声明的。但是如果你看一下 §8.4.3:

A function definition of the form:

attribute-specifier-seqopt decl-specifier-seqopt declarator virt-specifier-seqopt = delete ;

is called a deleted definition. A function with a deleted definition is also called a deleted function.

A program that refers to a deleted function implicitly or explicitly, other than to declare it, is ill-formed.

因此,标准将 deleted 函数定义为 user-declared(如上所示),即使它们是 deleted 并且无法使用

然后,根据 §12.8,隐式移动构造函数未定义,因为用户声明(使用 = delete;)复制构造函数。

user-declared 表示 用户提供用户定义),明确默认= default)或显式删除= delete)与隐式默认/删除(例如您的移动构造函数)形成对比。

所以在你的情况下,移动构造函数被隐式删除,因为复制构造函数是显式 已删除(因此 用户声明)。

However, in that particular case, how would deleted copy constructor/assignment mess default move constructor/assignment?

不会,但是标准不会区分这种情况和复杂情况。

最短的答案是隐式定义的移动构造函数和显式删除的复制构造函数可能 在某些情况下是危险的,当你有一个 user-defined 析构函数并且没有 user-defined 复制构造函数时也是如此(参见 rule of three/five/zero)。现在,您可以争辩说用户定义的析构函数不会删除复制构造函数,但这只是语言中的一个缺陷,无法删除,因为它会破坏很多旧的(坏)程序。引用 Bjarne Stroustrup 的话:

In an ideal world, I think we would decide on “no generation” as the default and provide a really simple notation for “give me all the usual operations.” [...] Also, a “no default operations” policy leads to compile time errors (which we should have an easy way to fix), whereas a generate operations by default policy leads to problems that cannot be detected until run time.

您可以在 N3174=10-0164 中阅读更多相关信息。

请注意,大多数人都遵循 rule of three/five/zero,我认为您应该这样做。通过隐式删除默认的移动构造函数,标准是 "protecting" 你不会犯错误,并且在某些情况下应该通过删除复制构造函数来保护你很长时间(参见 Bjarne 的论文)。

如有兴趣可进一步阅读:

I think this question has practical importance because manual generation and esp. maintenance of such default functions is error prone, while at the same time, the (righteous) increase of the use of classes such as std::unique_ptr as class members made non-copyable classes much more common beasts than they used to be.

将移动构造函数标记为显式默认将解决此问题:

class Foo {
public:
  Foo() = default;
  Foo(Foo const &) = delete;
  Foo(Foo&&) = default;
};

你得到了一个带有默认移动构造函数的不可复制对象,在我看来这些显式声明比隐式声明更好(例如,只将移动构造函数声明为 default 而没有删除复制构造函数).