什么是C++中的复制/移动构造函数选择规则?什么时候发生移动复制后备?

Sap*_*Sap 15 c++ copy-constructor move-constructor c++11

第一个例子:

#include <iostream>
#include <memory>
using namespace std;

struct A {
    unique_ptr<int> ref;
    A(const A&) = delete;
    A(A&&) = default;
    A(const int i) : ref(new int(i)) { }
    ~A() = default;
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

它完美地运作.所以这里使用了MOVE构造函数.

让我们删除移动构造函数并添加一个副本:

#include <iostream>
#include <memory>
using namespace std;

struct A {
    unique_ptr<int> ref;
    A(const A&a) 
        : ref( a.ref.get() ? new int(*a.ref) : nullptr )
    {  }
    A(A&&) = delete;
    A(const int i) : ref(new int(i)) { }
    ~A() = default;
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

现在编译落在错误" 使用已删除的函数'A :: A(A &&)' "
所以MOVE构造函数是必需的,并且没有回退到COPY构造函数.

现在让我们删除copy-move和move-constructors:

#include <iostream>
#include <memory>
using namespace std;

struct A {
    unique_ptr<int> ref;
    A(const int i) : ref(new int(i)) { }
    ~A() = default;
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

它与" 使用已删除的函数'A :: A(const A&)' "编译错误有关.现在它需要一个COPY构造函数!
所以从移动构造函数到复制构造函数有一个回退(?).

为什么?有没有人知道它是如何符合C++标准的,以及在复制/移动构造函数中选择的实际规则是什么?

Jos*_*eld 9

在检查它是否为deleted 之前,选择它将要使用的功能.在复制构造函数可用且移动构造函数为deleted的情况下,移动构造函数仍然是两者中的最佳选择.然后它看到它是deleted并给你一个错误.

如果你有相同的例子,但实际上删除了移动构造函数,而不是使它成为deleted,你会看到它编译得很好并且回退使用复制构造函数:

#include <iostream>
#include <memory>
using namespace std;

struct A {
    unique_ptr<int> ref;
    A(const A&a) 
        : ref( a.ref.get() ? new int(*a.ref) : nullptr )
    {  }
    A(const int i) : ref(new int(i)) { }
    ~A() = default;
};

int main()
{
    A a[2] = { 0, 1 };
   return 0;
}
Run Code Online (Sandbox Code Playgroud)

这个类根本没有为它声明的移动构造函数(甚至没有隐式),因此无法选择它.

  • @user3544995 选择过程取决于声明哪些构造函数。与所有重载情况一样,它将选择最匹配的构造函数。是的,“删除”的构造函数和不存在的构造函数*不一定*相同。不过,不存在的构造函数可能会被隐式声明为“删除”。在这种情况下,它不是 - 它只是根本没有隐式声明。 (2认同)

M.M*_*M.M 8

没有"后备".它被称为重载分辨率.如果在重载决策中有多个可能的候选者,则根据一组复杂的规则选择最佳匹配,您可以通过阅读C++标准或其草稿来找到这些规则.

这是一个没有构造函数的例子.

class X { };

void func(X &&) { cout << "move\n"; }            // 1
void func(X const &)  { cout << "copy\n"; }      // 2

int main()
{
    func( X{} );
}
Run Code Online (Sandbox Code Playgroud)
  • 原样:打印"移动"
  • 注释掉"1":打印"复制"
  • 注释掉"2":打印"移动"
  • 注释掉"1"和"2":无法编译

在重载决策中,绑定rvalue到rvalue的优先级高于左值的rvalue.


这是一个非常相似的例子:

void func(int) { cout << "int\n"; }      // 1
void func(long) { cout << "long\n"; }    // 2

int main()
{
     func(1);
}
Run Code Online (Sandbox Code Playgroud)
  • 原样:打印"int"
  • 注释掉"1":打印"长"
  • 注释掉"2":打印"int"
  • 注释掉"1"和"2":无法编译

在重载分辨率中,完全匹配优先于转换.


在这个主题的三个例子中,我们有:

1:两个候选功能; rvalue更喜欢rvalue(如我的第一个例子)

A(const A&);
A(A&&);           // chosen
Run Code Online (Sandbox Code Playgroud)

2:两个候选功能; rvalue更喜欢rvalue(如我的第一个例子)

A(const A&); 
A(A&&);           // chosen
Run Code Online (Sandbox Code Playgroud)

3:一个候选功能; 没有比赛

A(const A&);      // implicitly declared, chosen
Run Code Online (Sandbox Code Playgroud)

正如前面所解释的,有A(A &&)的情况下3没有隐含声明,因为你有一个析构函数.

对于重载解析,函数体是否存在并不重要,它是否声明了函数(显式或隐式).