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

时间:2022-01-17 16:17:06

拷贝构造函数的标准写法如下:

class  Base
{
public :
  Base(){}
  Base(
const  Base  & b){..}
  
// 为什么拷贝构造函数必须为引用传递,不能是值传递?
}

上述写法见得最多,甚至你认为理所当然。
那么如果我们不写成引用传递呢,而是值传递,那么会怎样?
class  Base
{
public :
  Base(){}
  Base(
const  Base b){为什么拷贝构造函数必须为引用传递,不能是值传递?}
  
// 为什么拷贝构造函数必须为引用传递,不能是值传递?
}
编译出错:error C2652: 'Base' : illegal copy constructor: first parameter must not be a 'Base'

事实上,你可以从这个小小的问题认真搞清楚2件事:
1) 拷贝构造函数的作用就是用来复制对象的,在使用这个对象的实例来初始化这个对象的一个新的实例。其实,个人认为不应该叫这些constructor(default constructor, copy constructor....)为构造函数,更佳的名字应该是"初始化函数"(见我的另一片文章).

2) 参数传递过程到底发生了什么?
    将地址传递和值传递统一起来,归根结底还是传递的是"值"(地址也是值,只不过通过它可以找到另一个值)!
i)值传递:
    对于内置数据类型的传递时,直接赋值拷贝给形参(注意形参是函数内局部变量);
    对于类类型的传递时,需要首先调用该类的拷贝构造函数来初始化形参(局部对象);如void foo(class_type obj_local){}, 如果调用foo(obj);  首先class_type obj_local(obj) ,这样就定义了局部变量obj_local供函数内部使用
ii)引用传递:
    无论对内置类型还是类类型,传递引用或指针最终都是传递的地址值!而地址总是指针类型(属于简单类型), 显然参数传递时,按简单类型的赋值拷贝,而不会有拷贝构造函数的调用(对于类类型).

上述1) 2)回答了为什么拷贝构造函数使用值传递会产生无限递归调用...

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

上述3)回答了在类中有指针数据成员时,拷贝构造函数使用值传递等于白显式定义了拷贝构造函数,因为默认的拷贝构造函数就是这么干的...