拷贝构造函数

定义及使用场景

拷贝构造函数是一种特殊的构造函数,它在创建对象时,是使用同一类中之前创建的对象来初始化新建的对象。拷贝构造函数通常用于:

  1. 通过使用另一个同类型的的对象来初始化新创建的对象
class Test;
Test A;
Test B(A);
  1. 复制对象把它作为参数传递给函数
void test(Test a)
{
}
  1. 复制对象,并从函数返回这个对象
Test test()
{
}

浅拷贝与深拷贝

相比与一般的普通类型赋值,类对象的赋值要复杂很多,因为类的成员变量很多,各种类型都会包括,所以会很复杂。

浅拷贝

所谓浅拷贝,指的是在对象赋值时,只对对象中的成员进行简单的赋值,默认的拷贝函数执行的也是浅拷贝。它具有以下一般形式:

Test::Test(const Test& r)
{
    width=r.width;
    height=r.height;
}

但是,当成员变量中有需要动态分配内存的变量时,简单来说就是指针变量时,使用浅拷贝就会出现问题,如一下代码:

#include
#include
using namespace std;
class Test
{
public:
    Test()
    {
     p=new int(100);
    }
   
    ~Test()
    {
     assert(p!=NULL);
        delete p;
    }
private:
    int width;
    int height;
    int *p;
};
int main()
{
    Test test1;
    Test test2(test1);
    return 0;
}

执行上面的代码,就会出错,主要原因就是定义的动态变量p,它的同一内存空间会被析构两次,所以会运行错误。因此,我们不能直接把test1对象直接复制给test2对象,而是应该重新为p分配内存。解决该错误的方法就是使用深拷贝。

深拷贝

在“深拷贝”的情况下,对于对象中动态成员,就不能仅仅简单地赋值了,而应该重新动态分配空间,如上面的例子就应该按照如下的方式进行处理:

#include
#include
using namespace std;
class Test
{
public:
    Test()
    {
     p=new int(100);
    }
    
    Test(const Test& r)
    {
     width=r.width;
        height=r.height;
     p=new int(100);
        *p=*(r.p);
    }
     
    ~Test()
    {
     assert(p!=NULL);
        delete p;
    }
private:
    int width;
    int height;
    int *p;
};
int main()
{
    Test test1;
    Test test2(test1);
    return 0;
}

此时test1的p和test2的p各自指向一段内存空间,但它们指向的空间具有相同的内容,这就是所谓的“深拷贝”。

总结

当出现类的等号赋值时,会调用拷贝函数,在未定义显示拷贝构造函数的情况下,系统会调用默认的拷贝函数——即浅拷贝,它能够完成成员的一一复制。当数据成员中没有指针时,浅拷贝是可行的。但当数据成员中有指针时,如果采用简单的浅拷贝,则两类中的两个指针将指向同一个地址,当对象快结束时,会调用两次析构函数,而导致指针悬挂现象。所以,这时,必须采用深拷贝。
深拷贝与浅拷贝的区别就在于深拷贝会在堆内存中另外申请空间来储存数据,从而也就解决了指针悬挂的问题。简而言之,当数据成员中有指针时,必须要用深拷贝。

  1. 为什么拷贝构造函数必须是引用传递,不能是值传递?

    简单的回答是为了防止递归引用。当一个对象需要以值方式传递时,编译器会生成代码调用它的拷贝构造函数以生成一个复本。如果类A的拷贝构造函数是以值方式传递一个类A对象作为参数的话,当需要调用类A的拷贝构造函数时,需要以值方式传进一个A的对象作为实参; 而以值方式传递需要调用类A的拷贝构造函数;结果就是调用类A的拷贝构造函数导致又一次调用类A的拷贝构造函数,这就是一个无限递归。

  2. 拷贝构造函数的作用。

    作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。

  3. 参数传递过程到底发生了什么?

    将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!

  4. 在类中有指针数据成员时,拷贝构造函数的使用?

    如果不显式声明拷贝构造函数的时候,编译器也会生成一个默认的拷贝构造函数,而且在一般的情况下运行的也很好。但是在遇到类有指针数据成员时就出现问题 了:因为默认的拷贝构造函数是按成员拷贝构造,这导致了两个不同的指针(如ptr1=ptr2)指向了相同的内存。当一个实例销毁时,调用析构函数 free(ptr1)释放了这段内存,那么剩下的一个实例的指针ptr2就无效了,在被销毁的时候free(ptr2)就会出现错误了, 这相当于重复释放一块内存两次。这种情况必须显式声明并实现自己的拷贝构造函数,来为新的实例的指针分配新的内存。

参考链接

C++拷贝构造函数详解

C++拷贝构造函数

C++拷贝构造函数详解

C++构造函数以及析构函数的若干面试问题

C++拷贝构造函数(深拷贝,浅拷贝)

你可能感兴趣的:(拷贝构造函数)