29

根据标准,

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

— X 没有用户声明的复制构造函数,

— X 没有用户声明的复制赋值运算符,

— X 没有用户声明的移动赋值运算符,并且

— X 没有用户声明的析构函数。

现在以下无法编译

# 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;
}

所以似乎删除的函数被认为是用户定义的,这是有道理的(它不是它的默认实现)。但是,在那种特殊情况下,如何删除复制构造函数/赋值混乱默认移动构造函数/赋值?

我认为这个问题具有实际意义,因为手动生成和 esp。维护这些默认函数很容易出错,同时,类成员(std::unique_ptr如类成员)使用的(正义的)增加使得不可复制的类比过去更常见。

4

3 回答 3

40

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

因此,在您的情况下,的,移动构造函数被隐式删除,因为复制构造函数被显式删除(因此user-declared)。

但是,在那种特殊情况下,如何删除复制构造函数/赋值混乱默认移动构造函数/赋值?

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

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

在一个理想的世界里,我认为我们会决定“不生成”作为默认值,并为“给我所有常用操作”提供一个非常简单的符号。[...]此外,“无默认操作”策略会导致编译时错误(我们应该有一种简单的方法来修复),而默认策略下的生成操作会导致直到运行时才能检测到的问题。

您可以在N3174=10-0164中阅读有关此内容的更多信息。

请注意,大多数人都遵循三/五/零的规则,我认为你应该这样做。通过隐式删除默认的移动构造函数,标准是“保护”您免受错误,并且在某些情况下应该通过删除复制构造函数来保护您很长时间(参见 Bjarne 的论文)。

感兴趣的可以继续阅读:

我认为这个问题具有实际意义,因为手动生成和 esp。维护这些默认函数很容易出错,同时,类成员(std::unique_ptr如类成员)使用的(正义的)增加使得不可复制的类比过去更常见。

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

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

您会得到一个带有默认移动构造函数的不可复制对象,并且在我看来,这些显式声明比隐式声明更好(例如,仅将移动构造函数声明为default不删除复制构造函数)。

于 2016-05-17T13:26:44.850 回答
2

正如你所说,从§12.8

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

  • X 没有用户声明的复制构造函数,

  • [...]

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

形式的函数定义:

属性说明符序列选择decl 说明符序列选择声明符 virt 说明符序列选择= 删除;

称为已删除定义。具有已删除定义的函数也称为已删除函数。

隐式或显式引用已删除函数的程序,而不是声明它,是格式错误的。

因此标准将deleted 函数定义为用户声明的(从上面可以看到),即使它们是deleted 并且不能使用。

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

于 2016-05-17T13:25:52.550 回答
-1

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

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

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

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

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

重载解决方案只看到一个候选者,但由于它被明确删除,因此程序格式错误。

您引用的部分下方的非规范性说明澄清了正在发生的事情:

[注意:当移动构造函数没有被隐式声明或显式提供时,否则会调用移动构造函数的表达式可能会调用复制构造函数。——尾注]

于 2016-05-17T13:11:16.857 回答