• 详解拷贝构造函数&拷贝赋值运算符


    参考书籍: c++ primer 5

    拷贝构造函数

    • 定义

      • 如果一个构造函数的第一个参数是自身类型的引用,且任何额外参数都有默认值,则此构造函数是拷贝构造函数。
    • 何时发生拷贝初始化(即,调用拷贝构造函数)

      1. =定义变量时。
        • 简记:=定义变量 。
      2. 将一个对象作为实参,传递给一个非引用类型的形参。
        • 简记:拷贝传参。
      3. 从一个返回类型为非引用类型的函数返回一个对象。
        • 简记:拷贝返回。
      4. 用花括号列表初始化一个数组中的元素或一个聚合类中的成员。
        • 简记:花括号列表初始化。

    使用的例子:

    string a("hello world");
    
    • 1

    拷贝赋值运算符

    先了解一些重载运算符:
    重载运算符本质上是函数,其名字由operator关键字后跟运算符组成。
    如:operator=

    • 重载赋值运算符的参数表示运算符的运算对象。

    • 某些运算符,包括赋值运算符,必须定义为成员函数

    • 如果一个运算符是成员函数,其左侧对象就绑定到隐式的this参数。

    • 对于二元的运算符,如赋值运算符,其右侧运算对象作为显式参数传递

    • 拷贝赋值运算符规范

      • 为了与内置类型的赋值保持一致,赋值运算符通常返回一个指向其左侧运算对象的引用

    标准库通常要求保存在容器中的类型要具有赋值运算符,且其返回值是左侧对象的引用。

    和拷贝构造函数类似,拷贝赋值运算符也是用于拷贝。
    但是拷贝构造函数强调构造,拷贝复制运算符强调赋值。

    为了确保你理解了这一点,给一个例子:

    class A {
    public:
    	A() = default;
    	A (int a) : a (a) { cout << "普通构造函数\n"; }
    	A (const A &right) : a (right.a) { cout << "拷贝构造函数\n"; }
    	int a;
    };
    
    int main (void)
    {
    	A num (99);    // 普通构造函数:直接初始化
    	A num1 = 99;    // 普通构造函数:拷贝形式初始化
    	num = 99;        // 普通构造函数:赋值,隐式调用了普通构造函数
    
        A num2(num);     //拷贝构造函数:直接初始化
        A num3 = num;     //拷贝构造函数:拷贝形式初始化
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这个比较简单,输出即是注释标明的结果。

    如果我们为A定义了拷贝赋值运算符,那么结果就会有变:

    class A {
    public:
    	A() = default;
    	A (int a) : a (a) { cout << "普通构造函数\n"; }
    	A (const A &right) : a (right.a) { cout << "拷贝构造函数\n"; }
    	A &operator= (int a) {cout << "拷贝赋值运算符\n"; return *this;}
    	int a;
    };
    
    int main (void)
    {
    	A num (99);    // 普通构造函数:直接初始化
    	A num1 = 99;    // 普通构造函数:拷贝形式初始化
    	num = 99;        // 拷贝赋值运算符:赋值
    
        A num2(num);     //拷贝构造函数:直接初始化
        A num3 = num;     //拷贝构造函数:拷贝形式初始化
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18

    关于num=99为什么会走向不同的结果,这是因为没有对应拷贝赋值运算符的num=99会自动调用普通构造函数,构造一个临时的A类,然后再调用编译器合成的拷贝赋值运算符——(它接受一个A的引用)。

    class A {
    public:
    	A() = default;
    	A (int a) : a (a) { cout << "普通构造函数\n"; }
    	A (const A &right) : a (right.a) { cout << "拷贝构造函数\n"; }
    	//A &operator= (int a) {cout << "拷贝赋值运算符\n"; return *this;}
    	A &operator=(const A&right){ cout << "拷贝赋值运算符2\n"; return *this; }
    	
    	int a;
    };
    
    int main (void)
    {
    	A num (99);
    	A num1 = 99;
    	num1 = 99;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    这时你会看到有四条输出:

    普通构造函数
    普通构造函数
    普通构造函数
    拷贝赋值运算符2
    
    • 1
    • 2
    • 3
    • 4

    而如果将A &operator=(const A&right)定义为delete,就无法编译通过。

    此外,当定义了接受string的拷贝赋值运算符时,这不符合我对预期:

    class A {
    public:
    	A() = default;
    	A (int a) : a (a) { cout << "普通构造函数\n"; }
    	A (const A &right) : a (right.a) { cout << "拷贝构造函数\n"; }
    	
    	A &operator= (string a) { cout << "无用的\n"; return *this;};
    	//A &operator= (int a) {cout << "拷贝赋值运算符\n"; return *this;}
    	//A &operator=(const A&right){ cout << "拷贝赋值运算符2\n"; return *this; }
    	
    	int a;
    };
    
    int main (void)
    {
    	A num (99);
    	A num1 = 99;
    	num1 = 99;
    	
    	A num2 (num);
    	A num3 = num;
    }
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22

    因为合成的拷贝赋值运算符,应该只在一个类未定义拷贝赋值运算符时,这里依然正常工作了,也许是编译器更智能了,我使用-std=c++11

  • 相关阅读:
    [第十二篇]——Docker Dockerfile
    前后端开发环境下载,java web前后端分离项目所有环境下载
    Python中的@lru_cache装饰器
    机器学习实训(4)——支持向量机(补充)
    如何通过Java代码在Word中创建可填充表单
    mac M1 安装AndroidStudio打开真机调试
    【Java 进阶篇】Java Request 请求转发详解
    CDC Schemes
    JVM coredump
    对Spring AOP的进一步深入理解
  • 原文地址:https://blog.csdn.net/qq_51470638/article/details/127549686