什么是复制/移动构造函数在C ++中选择规则?什么时候移动到复制发生? [英] What is copy/move constructor choosing rule in C++? When does move-to-copy fallback happen?

查看:189
本文介绍了什么是复制/移动构造函数在C ++中选择规则?什么时候移动到复制发生?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

第一个示例:

  #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构造函数。


让我们删除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 ;&)= 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;
}

A(const A&)'编译错误。现在它需要一个COPY构造函数!

所以有一个从move构造函数到复制构造函数的回退(?)。


为什么?有没有人知道它是如何符合C ++标准和什么是复制/移动构造函数之间选择的规则?

解决方案

p>没有后备。它被称为重载分辨率。如果在重载解析中有多个可能的候选项,则根据一组复杂的规则选择最佳匹配,您可以通过阅读C ++标准或其草稿来找到它们。



这里是没有构造函数的示例。

  class X {}; 

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

int main()
{
func(X {});
}




  • 原样:列印「move」

  • 注释掉1:printscopy

  • 注释掉2 li>注释掉1和2:无法编译



在重载解析中,




$ b




$ b

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

int main()
{
func(1);
}




  • 原样:printsint

  • 注释掉1:printslong

  • li>注释掉1和2:无法编译



在重载解析中, 。






在这个线程的三个例子中,我们有:



1:两个候选函数; rvalue偏好rvalue(如我的第一个例子)

  A(const A& 
A(A&&); // selected

2:两个候选函数; rvalue偏好rvalue(如我的第一个例子)

  A(const A& 
A(A&&); // selected

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

  A(const A&); //隐式声明,选择

作为前面解释过, (A&&)在case 3,因为你有一个析构函数。



对于重载分辨率无论函数体是否存在, (显式或隐式)。


The first example:

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

It works perfectly. So here the MOVE constructor is used.

Let's remove the move constructor and add a copy one:

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

Now the compilation falls with the error "use of deleted function ‘A::A(A&&)’"
So the MOVE constructor is REQUIRED and there is no fall back to a COPY constructor.

Now let's remove both copy- and 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;
}

And it falls with "use of deleted function ‘A::A(const A&)’" compilation error. Now it REQUIRES a COPY constructor!
So there was a fallback (?) from the move constructor to the copy constructor.

Why? Does anyone have any idea how does it conform to the C++ standard and what actually is the rule of choosing among copy/move constructors?

解决方案

There is no "fallback". It is called overload resolution. If there are more than one possible candidate in overload resolution then the best match is chosen, according to a complicated set of rules which you can find by reading the C++ standard or a draft of it.

Here is an example without constructors.

class X { };

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

int main()
{
    func( X{} );
}

  • As-is: prints "move"
  • Comment out "1": prints "copy"
  • Comment out "2": prints "move"
  • Comment out "1" and "2": fails to compile

In overload resolution, binding rvalue to rvalue has higher preference than lvalue to rvalue.


Here is a very similar example:

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

int main()
{
     func(1);
}

  • As-is: prints "int"
  • Comment out "1": prints "long"
  • Comment out "2": prints "int"
  • Comment out "1" and "2": fails to compile

In overload resolution, an exact match is preferred to a conversion.


In your three examples on this thread we have:

1: Two candidate functions; rvalue prefers rvalue (as in my first example)

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

2: Two candidate functions; rvalue prefers rvalue (as in my first example)

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

3: One candidate function; no contest

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

As explained earlier, there is no implicit declaration of A(A&&) in case 3 because you have a destructor.

For overload resolution it does not matter whether the function body exists or not, it is whether the function is declared (either explicitly or implicitly).

这篇关于什么是复制/移动构造函数在C ++中选择规则?什么时候移动到复制发生?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆