为什么删除模板复制构造函数会导致赋值运算符失效?

问题描述

我有像下面这样的代码,看起来有点令人困惑。我定义了一个模板类。它有一个用户定义的构造函数。当我通过“operator =”声明这个模板类的两个对象时,令我惊讶的是它的用户定义构造函数调用。此外,删除其复制构造函数后,在解析“operator =”时甚至无法通过编译。模板构造函数与非模板类的规则是否不同?

#include "iostream"
using namespace std;

template <typename T>
class MyPtr
{
private:
    T p_;
public:
    MyPtr(T P = NULL): p_(P)
    {
        cout<<"track!!"<<endl;
    }

    //MyPtr(const MyPtr<T> & P) = delete;
    ~MyPtr()
    {
 
    }
};

int main()
{
    int i=3;
    int j=4;
    MyPtr<int> p = i;
    MyPtr<int> pp = j;
}

解决方法

模板构造函数与非模板类的规则是否不同?

没有

MyPtr<int> p = i;(和 MyPtr<int> pp = j;)是 copy initialization。请注意,这是初始化,而不是赋值,因为效果 p 是由构造函数初始化的MyPtr::MyPtr(T)。例如

MyPtr<int> p = i; // initialization; constructors get called
p = j;            // assignment; assignment operators (operator=) get called

在 C++17 之前,i 会先通过 MyPtr<int> 转换为 MyPtr::MyPtr(T),然后转换结果,即临时 MyPtr<int> 被复制/移动以初始化p;即使允许优化复制/移动操作,也要求复制/移动构造函数可访问。将复制构造函数声明为 delete 会使复制构造函数不可用并且不会生成移动构造函数,从而使 MyPtr<int> p = i; 在 C++17 之前格式错误。

由于 C++17 优化是强制性的,并且不需要再次访问复制/移动构造函数,这意味着即使将复制构造函数声明为 delete,您的代码也可以在 C++17 模式下正常编译。

  • 如果 T 是类类型,并且 other 的类型的 cv 非限定版本不是 T 或从 T 派生,或者如果 T 是非类类型,但 other 的类型是类类型,{{ 3}} 可以从 other 的类型转换为 T(或者如果 T 是类类型并且转换函数可用,则转换为从 T 派生的类型),并通过重载决议选择最好的。如果使用了 user-defined conversion sequences,则转换结果为 prvalue temporary (until C++17) prvalue expression (since C++17),然后用于 converting constructor 对象。 The last step is usually optimized out and the result of the conversion is constructed directly in the memory allocated for the target object,but the appropriate constructor (move or copy) is required to be accessible even though it's not used. (until C++17)