C++ 默认移动构造函数/分配和已删除的复制构造函数/分配

C++ 默认移动构造函数/分配和已删除的复制构造函数/分配,c++,move-semantics,deleted-functions,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::

按照标准,

如果类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;
}
#包括
福班
{
公众:
Foo()=默认值;
Foo(Foo const&)=删除;
};
int main()
{
福福;
Foo g(std::move(f));//此处编译失败
返回0;
}
因此,似乎删除的函数被认为是用户定义的,这是有意义的(这不是它的默认实现)。但是,在这种特殊情况下,删除的复制构造函数/赋值将如何移动默认构造函数/赋值


我认为这个问题具有实际的重要性,因为手动生成和维护这些默认函数容易出错,而类成员使不可复制类变得比以前更为常见,因此类的使用增加了,例如
std::unique\u ptr

这种行为基本上可以用以下内容来说明:

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

void foo(int&& i)
{
    std::cout << "move";
}
重载解析只看到一个候选项,但由于它被显式删除,程序的格式不正确

您引用的章节下方的非规范性说明澄清了这一点:

[注意:如果未隐式声明或显式提供移动构造函数,则会调用 移动构造函数可能会调用复制构造函数。-结束说明 ]


如您所述,从§12.8开始

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

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

  • [……]

注意,用户声明了。但如果你看看§8.4.3:

表单的函数定义:

属性说明符seqopt decl说明符seqopt声明符virt说明符seqopt=delete

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

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

因此,标准将
delete
d函数定义为用户声明的(如上图所示),即使它们是
delete
d且无法使用


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

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

因此,在您的例子中,yes移动构造函数被隐式删除,因为复制构造函数被显式删除(因此用户声明)

但是,在这种特殊情况下,删除的复制构造函数/赋值将如何移动默认构造函数/赋值

不会,但标准并不能区分这起案件和复杂案件

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

在理想情况下,我认为我们会将“无生成”作为默认值,并为“给我所有常规操作”提供一个非常简单的符号。[……]此外,“无默认操作”策略会导致编译时错误(我们应该有一种简单的方法来修复),而默认生成操作策略会导致在运行时之前无法检测到的问题

您可以在中阅读更多关于此的信息

请注意,大多数人都遵循这个标准,我认为你应该这样做。通过隐式删除默认的移动构造函数,该标准可以“保护”你不犯错误,并且在某些情况下应该在很久以前通过删除复制构造函数来保护你(参见Bjarne的论文)

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

我认为这个问题具有实际重要性,因为手动生成和维护此类默认函数容易出错,同时,类成员(正当地)增加了对类的使用,例如
std::unique_ptr
,这使得不可复制类比以前更为常见

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

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

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

不一定能很好地理解你的问题,但是为什么不默认移动向量呢?也就是说,
Foo(Foo&&)=default;
如果类是不可移动的,那么它几乎肯定是不可复制的。不可复制但可移动的cl是很常见的
class Foo {
public:
  Foo() = default;
  Foo(Foo const &) = delete;
  Foo(Foo&&) = default;
};