【问题标题】:What is copy/move constructor choosing rule in C++? When does move-to-copy fallback happen?什么是 C++ 中的复制/移动构造函数选择规则?移动到复制回退何时发生?
【发布时间】:2014-09-01 20:32:33
【问题描述】:

第一个例子:

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

完美运行。所以这里使用了 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;
}

现在编译出现错误“use of deleted function ‘A::A(A&&)’
所以 MOVE 构造函数是必需的,并且没有回退到 COPY 构造函数。

现在让我们删除复制和移动构造函数:

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

它属于“use of deleted function ‘A::A(const A&)’”编译错误。现在它需要一个 COPY 构造函数!
所以从移动构造函数到复制构造函数有一个后备(?)。

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

【问题讨论】:

  • 半小时前我不是已经回答了这个问题吗?>.>
  • 这个问题要具体得多。这不是同一个问题。

标签: c++ c++11 copy-constructor move-constructor


【解决方案1】:

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

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

class X { };

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

int main()
{
    func( X{} );
}
  • 原样:打印“移动”
  • 注释掉“1”:打印“副本”
  • 注释掉“2”:打印“move”
  • 注释掉“1”和“2”:编译失败

在重载决议中,将右值绑定到右值比左值到右值具有更高的优先级。


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

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

int main()
{
     func(1);
}
  • 原样:打印“int”
  • 注释掉“1”:打印“long”
  • 注释掉“2”:打印“int”
  • 注释掉“1”和“2”:编译失败

在重载解析中,精确匹配优于转换。


在您在此线程上的三个示例中,我们有:

1:两个候选函数;右值更喜欢右值(在我的第一个例子中)

A(const A&);
A(A&&);           // chosen

2:两个候选函数;右值更喜欢右值(在我的第一个例子中)

A(const A&); 
A(A&&);           // chosen

3:一个候选函数;没有比赛

A(const A&);      // implicitly declared, chosen

作为explained earlier,在情况 3 中没有隐式声明 A(A&&),因为您有一个析构函数。

对于重载决议,函数体是否存在并不重要,重要的是函数是否被声明(显式或隐式)。

【讨论】:

    【解决方案2】:

    在检查它是否为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;
    }
    

    这个类根本没有为它声明移动构造函数(甚至没有隐式声明),因此它不能被选择。

    【讨论】:

    • 所以选择(选择过程)取决于它是一个构造函数还是两个?这似乎是有线的。那么一个被删除的构造函数和一个不存在的构造函数是不一样的吗?
    • @user3544995 选择过程取决于声明了哪些构造函数。与所有重载情况一样,它将选择最佳匹配的构造函数。是的,deleted 构造函数和缺席构造函数不一定相同。但是,可以将不存在的构造函数隐式声明为 deleted。在这种情况下它不是 - 它根本没有隐式声明。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-11-07
    • 2023-03-11
    • 1970-01-01
    相关资源
    最近更新 更多