默认移动构造函数/赋值和已删除的复制构造函数/赋值

时间:2016-05-17 12:36:15

标签: c++ move-semantics deleted-functions

根据标准,

  

如果类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;
}

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

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

3 个答案:

答案 0 :(得分:22)

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

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

  

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

它不会,但标准并没有区分这个案例和复杂案例。

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

  

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

您可以在N3174=10-0164中详细了解相关信息。

请注意,大多数人都会关注rule of three/five/zero,我认为您应该这样做。通过隐式删除默认的移动构造函数,标准是&#34;保护&#34;你在错误的情况下,应该在很长一段时间内通过删除复制构造函数来保护你(参见Bjarne的论文)。

如果您有兴趣,请进一步阅读:

  

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

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

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

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

答案 1 :(得分:2)

如你所说,来自§12.8

  

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

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

  •   
  • [...]

  •   

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

  

表单的函数定义:

     

attribute-specifier-seq opt decl-specifier-seq opt declarator virt-specifier-seq opt = delete; < / em>的

     

称为已删除的定义。具有已删除定义的功能也称为已删除功能。

     

除了声明之外,隐式或明确引用已删除函数的程序格式不正确。

因此标准将string[] arr1 = new string[] { "one", "two", "three" }; var variations_hash = new Dictionary<string, List<string>>(); foreach(var item1 in arr1) { List<String> list; if (variations_hash.TryGetValue("v_id_1", out list)) list.Add(item1); else variations_hash.Add("v_id_1", new List<String>() {item1}); } ... d函数定义为用户声明的(如上所示),即使它们是delete d并且无法使用

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

答案 2 :(得分:-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;
};

重载分辨率只能看到一个候选者,但由于它被明确删除,因此该程序格式不正确。

您引用的部分下面的非规范性说明澄清了这是发生的事情:

  

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