美文网首页
一文读懂C++拷贝构造函数?

一文读懂C++拷贝构造函数?

作者: TangFly | 来源:发表于2020-04-23 15:43 被阅读0次

    C++面试中,经常有面试官问“请你说一下C++拷贝构造函数是什么?“。而我们在阅读STL等很多C++的源码时,我们也经常看到作者对拷贝构造函数的处理。可以说,在学习C++的过程中,拷贝构造函数的理解是衡量一个人对C++了解深度的一个必考点。答得好,恭喜你入门了,否则可以说你还没有入门。那么,在此我将总结下这块的相关知识给大家参考理解。下面,我将从3个方面来说明这个知识点。

    1. 拷贝构造函数是什么?

    先直接上一段拷贝构造函数的代码,如下:

    class MString

    {

         MString(const MString& other) 

        {

                m_pChar = new char[strlen(other.m_pChar)+1];

                assert( nullptr != m_pChar);

                strcpy(m_pChar, other.m_pChar);

         }

    private:

         char*  m_pChar;        

    };

    拷贝构造函数的形式是: Class(const Class& other)。首先,它本质上就是一个构造函数;其次,它实现的方式是从一个其他同类对象复制过来的。所以,和普通构造函数不一样的是,它是从另一个类对象中”克隆“过来的。既然是”克隆“,那么就是涉及到2个问题:1)什么情况下需要这种方式的”克隆“?2)怎么”克隆“? 对此,下面继续回答这两个问题。

    2. 拷贝构造函数什么时候会被用到?

    拷贝构造函数的使用场景有3类。代码形式分别如下:

    class X { ...... };

    X x;

    X y;

    X yy = x; // 第1类:用存在的类X的对象x,来初始化一个新声明的类对象yy。这句话等同于X yy(x);

    y = x; // 这里,就不是了,因为y是已经存在了,所以这里调用的是类的operator=()函数。

    void call(X x);

    void bar()

    {

        X xx;

        call(xx); //第2类:类对象xx作为函数的传值参数。进函数call时,会发生值copy。函数内部就会先调用类的拷贝构造函数创造一个值copy,即一个新的类对象副本在函数内使用。

    ...

    }

    X  funcall() 

    {

        X zz;

       //....

        return zz; //第3类:用类对象作为函数的返回值时,会先调用类的拷贝构造函数生成一个类副本对象,用作后面的返回值对象。然后再执行zz对象的析构。

    }

    对此,我们可以看到,类对象的初始化、函数传值和函数对象返回时,都是调用类的拷贝构造函数来完成的。这么看,怎么保证这个类对象被正确的”克隆“呢?下面回答这个问题。

    3.拷贝构造函数怎么执行“克隆”的?

    我们知道,拷贝构造函数我们一般可以不写的。如下类:

    class 3DPoint 

    {

        float  m_x;

        float  m_y;

        float m_z;

    };

    void test()

    {

        3DPoint  p1;

        3DPoint p2 = p1;//这里,编译器会自动生成一个默认的拷贝构造函数来复制对象。

       .......

    }

    test()函数执行正常。那么如果将这个类改成:

    class 3DPoint 

    {

    public:

        ~3DPoint()

       {

             if(m_x)

                   delete m_x;

             if(m_y)

                   delete m_y;

             if(m_z)

                   delete m_z;

        }

        float *m_x;

        float  *m_y;

        float *m_z;

    };

    void test()

    {

        3DPoint  p1;

        3DPoint p2 = p1;

       .......

    }

    函数test()执行正常吗?对此,我们必须了解下, 如果没有实现,那么编译器给我们生成的拷贝构造函数采用什么方式来copy对象内容呢? 这里实际上执行的是“Default Memberwise Initialization" 方式。什么意思?就是对象成员的默认初始化方式。如果类成员里有类对象,就执行这个类对象的默认初始化方式。举个例子:

    class Cicle

    {

    float m_radius;

    3DPoint m_center;

    };

    这个类,编译器自动生成的拷贝构造函数就是大概这么实现的:

    Cicle(const Circle& other)

    {

            m_radius = other.m_radius;

            m_center = 3DPoint(other.m_radius);//调用成员类的copy构造函数。依次递归,直到最底层基本类型数据。

    }

    那么我们发现,最终调用的是基本数据类型的复制方式。那么基本类型的复制,就是一块内存里面的数据内容复制,如果没有说明指定方式,那么复制方式就是直接一个位一个位内容的copy。由此,我们现在来看改造类后的第二个test()函数的执行过程。在函数返回时,会调用对象p1、p2的析构,2个析构函数都会进行内存释放操作。如果上面改成指针后,却没有拷贝构造函数时,那么默认编译器会自动生成一个,但是默认生成的拷贝构造函数执行对象“克隆”时,采用的是3DPoint 的默认拷贝构造函数。那么,3DPoint p2 = p1; 这句执行后, 对象p2的成员m_x, m_y,m_z 指针就是将p1 中的成员 m_x, m_y,m_z 指针内容直接copy过来, 这样,就造成了两个类对象的指针指向的是同样的内存地址。因此p1,p2两个对象的成员m_x,m_y,m_z指针指向的对象是完全一样的。在析构时,就会造成同一块内存被2次释放!对此,解决方案,就是重写copy构造函数。对涉及到的成员指针对象进行“深copy”,即自己进行内存copy。

    总结: 拷贝构造函数的深入理解是C++入门的一个关键问题。对于一个类,尽可能的去自己实现其copy构造函数和operator=() 函数,而不要采用默认的生成函数。这样避免类内存方面的错误。

    相关文章

      网友评论

          本文标题:一文读懂C++拷贝构造函数?

          本文链接:https://www.haomeiwen.com/subject/rdkaihtx.html