C++的复制构造函数三种用法

本文详细探讨了C++中复制构造函数的调用情况,包括对象初始化、函数参数传递和函数返回值时的使用。特别强调了现代编译器对返回值优化(RVO)的实现,解释了为何有时函数返回对象时不调用复制构造函数。通过实例展示了全局对象作为返回值、函数参数为对象及函数返回值为对象时如何触发复制构造函数的调用。
摘要由CSDN通过智能技术生成

前言

如果真的想明白,为什么你写的函数的返回值是对象时,有的时候调用了复制构造函数,而有的时候,没有调用复制构造函数。需要明白一件事:函数的返回值是对象时,什么情况下,函数的返回是return by value,即生成了临时对象。这两个问题是等价的,因为按照道理来讲,对象作为函数的返回值时,就应该生成临时对象,调用复制构造函数,初始化这个临时对象,但是,现在的编译器,对于return by value有优化。有时候,不生产临时对象。编译器总是将栈里面的 结果对象 直接给调用者,避免了多一次的析构和构造。即使在关闭编译器优化的时候,它依然给你做了这个动作。
现在的我,还不明白具体什么情况下,生成临时对象。什么情况下,不生成临时对象。
留给以后解决
可以参考的文章参考1参考2

三种用法

我们知道,类、struct结构体的 复制构造函数 在三种情况下会被调用,分别是:

1、创建对象a时,使用对象b去初始化对象a
2、函数fun( )的形参是对象时,传递参数时,形参 是用 复制构造函数 初始化的
3、函数的返回值是对象时,生成的临时对象,是用 复制构造函数初始化的

接下来依次看一下这三种情况。

首先是类的定义

class node {
    public:
        int x, y;
        
        node () {} // 默认构造函数
        node (int _x, int _y) : x(_x), y(_y) {} // 自定义的构造函数

        node (const node& temp) { // 自定义的复制构造函数
            x = temp.x;
            y = temp.y;
            printf("复制构造函数被调用\n"); 
            // 打印语句,只要该复制构造函数被调用,就会有输出
        }
};

1、创建对象a时,使用别的对象b去初始化a

int main()
{
    node b(8, 10); 
    // 创建了node对象 b,并且调用了构造函数,初始化对象b
    node a(b); // 创建对象a, 并且使用对象b来初始化a
    // node a = b; 
    // 这样也可以,只要是创建对象同时,用其余对象初始化就可以。
    cout<< a.x << " " << a.y << endl;

    return 0;
}

运行结果

复制构造函数被调用
8 10

可以看到,先输出了"复制构造函数被调用",之后又执行了打印a.x、a.y的语句。说明,在创建对象a时,使用对象b初始化对象a,是会调用复制构造函数的。调用了对象a的复制构造函数,复制构造函数的实参是对象 b。
但是下面的这种情况就不是创建对象的同时使用另一个对象初始化了。

int main()
{
    node b(8, 10);
    node a; // 先创建了对象 a,使用默认构造函数初始化了对象 a

    a = b; // 这是赋值,不是初始化
    
    cout << a.x << " " << a.y << endl;

    return 0;
}

运行结果

8 10

可见,首先定义一个对象a,之后再进行赋值,就不是初始化了。

2、函数fun( )的形参是对象时,传递参数时,形参 是用 复制构造函数 初始化的

void show(node temp) { // show()函数的参数是对象
    cout << temp.x << " " << temp.y << endl;
}

int main()
{
    node a(8, 10); 
    // 创建了node对象 a,并且调用了构造函数,初始化数据

    show(a);
    // 将对象a作为函数的实参传入

    return 0;
}

运行结果

复制构造函数被调用
8 10

可以看到,首先打印的是复制构造函数中的语句“复制构造函数被调用”,之后执行的是函数体里面的打印语句。所以,对象作为函数的参数时,传递参数时,是会调用复制构造函数的。即进行了值复制。形参temp是使用实参 a 初始化的。初始化形参temp时,调用了temp的复制构造函数,这个函数的参数是a。
更进一步,如果参数是引用:

void show(node& temp) { // show()函数的参数是 对象的引用
    cout << temp.x << " " << temp.y << endl;
}

int main()
{
    node a(8, 10); 
    // 创建了node对象 a,并且调用了构造函数,初始化数据

    show(a);
    // 将对象a作为函数的实参传入

    return 0;
}

此时函数的参数不是对象本身,而是对象的引用。再来看一下运行结果:

8 10

如果函数的参数是 对象的引用时,传入参数的时候,没有调用复制构造函数,没有输出复制构造函数中的打印语句“复制构造函数被调用”。
所以这种情况下,就没有复制这一步,因为传入的是实参的引用。如果大量数据要作为实参传入时,最好传入的是引用。速度快。

3、 函数的返回值是对象时,生成的临时对象,是用 复制构造函数 初始化的

我们知道,不同的函数,是在不同的内存空间中运行的,调用某个函数fun()时,去往另一片内存空间运行,当该函数执行结束时,这一片内存空间要被释放,其中的变量要消亡。但是,如果该函数有返回值(无论是基本类型,还是对象),则该函数要生成一个临时的值temp,用于返回给调用函数的地方。因为该函数执行结束之后,该函数里面的任何局部变量都要消失。
重点就是,该临时值temp,如果是对象的话,就是使用复制构造函数进行初始化的。

node create_node() { // 函数的返回值是对象
    node temp(3, 5); // 创建一个对象
    return temp; // 返回对象
}

int main()
{
    node a = create_node();// a 用于接收函数返回值

    cout << a.x << " " << a.y << endl;

    return 0;
}

运行结果:

3 5

可以看到,并没有想象的那样输出“复制构造函数被调用”,也就是说明,此种情况下,并没有调用复制构造函数。
原因在于现在的编译器对于“对象作为函数的返回值”这种情况有所优化。返回值为对象时,有的情况下不再产生临时对象,因而不再调用复制构造函数。因此也就不存在临时对象被初始化。

有几种情况下,还是会调用复制构造函数。
全局对象 cur 作为函数返回值,对象 a 在接收 返回值 cur 时,调用复制构造函数。

#include <bits/stdc++.h>
using namespace std;

class node {
    public:
        int x, y;

        node() {}
        node(int _x, int _y) : x(_x), y(_y) {}

        node(const node& temp) {
            x = temp.x, y = temp.y;
            printf("复制构造函数被调用\n");
        }
};

node cur(1, 2);

node fun() { // 函数的返回值是对象,并且是全局对象
    return cur;
}

int main()
{
    node a ; // a用于接收函数返回值
    a = fun(); // a 接收函数返回值
    cout << a.x << " " << a.y << endl;

    return 0;
}

运行结果

复制构造函数被调用
1 2

可以看到,当全局对象 cur 作为函数返回值时,对象 a 在接收 返回对象 cur时,调用了复制构造函数。

详细地分析一下这个例子。fun( ) 函数执行结束后,会生成一个临时对象,假设为temp,该对象是使用对象cur进行初始化的。在这个初始化过程中,调用了temp的复制构造函数,其实参是 return 语句中的 cur。在这之后,fun( )函数彻底消失,占用的内存被释放。
之后,在main( ) 函数中的 a = fun(); 语句,该临时对象temp,被赋值给 a, 这是一个赋值语句,并不是初始化语句。该语句执行结束之后,临时对象 temp 消亡。

此外,如果有一个函数,它的参数是对象,返回值也是对象,则,接收该函数的返回值时,调用了复制构造函数。

#include <bits/stdc++.h>
using namespace std;

class node {
    public:
        int x, y;

        node() {}
        node(int _x, int _y) : x(_x), y(_y) {}

        node(const node& temp) {
            x = temp.x, y = temp.y;
            printf("复制构造函数被调用\n");
        }
};

node fun(node& temp) { // 此处是引用,否则调用两次赋值构造函数。
    return temp;
}

int main()
{
    node a(1, 2), b;
    b = fun(a);
    cout << a.x << " " << a.y << endl;

    return 0;
}

运行结果

复制构造函数被调用
1 2

可以看到,fun( ) 函数的参数是对象、返回值也是对象,此时,对象 b 接收 返回值时,就调用了复制构造函数。

总结

复制构造函数,只在对象被 初始化 时被调用。而初始化意味着某对象在创建的同时,被初始化。
具体就三种情况:
1、对象被创建时,被其余对象初始化
2、函数fun( ) 的形参是对象,则调用函数 fun()时,形参会被初始化
3、函数的返回值是 对象,则生成的临时对象会被 return 语句中的数据初始化。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值