复制构造函数是一种特殊构造函数,具有单个形参,该形参(常用 const 修饰)是对该类类型的引用。当定义一个新对象并用一个同类型的对象对它进行初始化时,将显式使用复制构造函数。当将该类型的对象传递给函数或函数返回该类型的对象时,将隐式使用复制构造函数。
析构函数是构造函数的互补:当对象超出作用域或动态分配的对象被删除时,将自动应用析构函数。析构函数可用于释放对象时构造或在对象的生命期中所获取的资源。不管类是否定义了自己的析构函数,编译器都自动执行类中非static 数据成员的析构函数。
复制构造函数、赋值操作符和析构函数总称为复制控制。编译器自动实现这些操作,但类也可以定义自己的版本。
通常,编译器合成的复制控制函数是非常精练的——它们只做必需的工作。但对某些类而言, 依赖于默认定义会导致灾难。 实现复制控制操作最困难的部分,往往在于识别何时需要覆盖默认版本。 有一种特别常见的情况需要类定义自己的复制控制成员的:类具有指针成员。
复制构造函数
只有单个形参,而且该形参是对本类类型对象的引用(常用 const 修饰),这样的构造函数称为复制构造函数。与默认构造函数一样,复制构造函数可由编译器隐式调用。复制构造函数可用于:
• 根据另一个同类型的对象显式或隐式初始化一个对象。
• 复制一个对象,将它作为实参传给一个函数。
• 从函数返回时复制一个对象。
• 初始化顺序容器中的元素。
• 根据元素初始化式列表初始化数组元素。
禁止复制
为了防止复制,类必须显式声明其复制构造函数为 private。
如果复制构造函数是私有的,将不允许用户代码复制该类类型的对象,编译器将拒绝任何进行复制的尝试。
然而,类的友元和成员仍可以进行复制。如果想要连友元和成员中的复制也禁止,就可以声明一个(private)复制构造函数但不对其定义。
赋值操作符
重载操作符是一些函数,其名字为 operator 后跟着所定义的操作符的符号。因此,通过定义名为 operator= 的函数,我们可以对赋值进行定义。像任何其他函数一样,操作符函数有一个返回值和一个形参表。形参表必须具有与该操作符数目相同的形参(如果操作符是一个类成员,则包括隐式 this 形参)。赋值是二元运算,所以该操作符函数有两个形参:第一个形参对应着左操作数,第二个形参对应右操作数。
大多数操作符可以定义为成员函数或非成员函数。当操作符为成员函数时,它的第一个操作数隐式绑定到 this 指针。有些操作符(包括赋值操作符)必须是定义自己的类的成员。因为赋值必须是类的成员,所以 this 绑定到指向左操作数的指针。因此,赋值操作符接受单个形参,且该形参是同一类类型的对象。右操作数一般作为 const 引用传递。
一般而言,如果类需要复制构造函数,它也会需要赋值操作符。
实际上,就将这两个操作符看作一个单元。如果需要其中一个,我们几乎也肯定需要另一个。
析构函数
构造函数的一个用途是自动获取资源。例如,构造函数可以分配一个缓冲区或打开一个文件,在构造函数中分配了资源之后,需要一个对应操作自动回收或释放资源。析构函数就是这样的一个特殊函数,它可以完成所需的资源回收,作为类构造函数的补充。
动态分配的对象只有在指向该对象的指针被删除时才撤销。
如果没有删除指向动态对象的指针,则不会运行该对象的析构函数,对象就一直存在,从而导致内存泄漏,而且,对象内部使用的任何资源也不会释放。
当对象的引用或指针超出作用域时,不会运行析构函数。只有删除指向动态分配对象的指针或实际对象(而不是对象的引用)超出作用域时,才会运行析构函数。
何时编写显式析构函数
许多类不需要显式析构函数,尤其是具有构造函数的类不一定需要定义自己的析构函数。仅在有些工作需要析构函数完成时,才需要析构函数。析构函数通常用于释放在构造函数或在对象生命期内获取的资源。
如果类需要析构函数,则它也需要赋值操作符和复制构造函数,这是一个有用的经验法则。这个规则常称为三法则,指的是如果需要析构函数,则需要所有这三个复制控制成员。
测试代码如下:
#include <vector>
#include <iostream>
struct Exmp1
{
//默认构造函数
Exmp1(){std::cout<<"Exmp1()"<<std::endl;}
//复制构造函数
Exmp1(const Exmp1&)
{std::cout<<"Exmp1(const Exmp1&)"<<std::endl;}
//赋值操作符
Exmp1& operator = (const Exmp1&abc)
{
std::cout<<"Operator:(const Exmp1&)"<<std::endl;
return *this;
}
//析构函数
~Exmp1(){std::cout<<"~Exmp1()"<<std::endl;}
};
void func1(Exmp1 obj) //形参为Exmp1对象
{
}
void func2(Exmp1& obj) //形参为Exmp1对象的引用
{
}
Exmp1 func3()
{
Exmp1 obj;
return obj; //返回Exmp1对象
}
int main()
{
Exmp1 eobj; //调用默认构造函数创建Exmp1对象eobj
func1(eobj); //调用复制构造函数
//将形参Exmp1对象创建为实参Exmp1对象的副本
//函数执行完毕后调用析构函数撤销形参Exmp1对象
func2(eobj); //形参为Exmp1对象的引用,无需调用复制构造函数传递实参
eobj = func3();//调用默认构造函数创建局部Exmp1对象
//函数返回时调用复制构造函数创建作为返回值副本的Exmp1对象
//然后调用析构函数撤销局部Exmp1对象
//然后调用赋值操作符
//执行完赋值操作符
//调用析构函数撤销作为返回值副本的Exmp1对象
Exmp1 *p = new Exmp1;//调用默认构造函数动态创建Exmp1对象
std::vector<Exmp1> evec(3);//调用3次默认构造函数
//然后调用3次析构函数撤销局部Exmp1对象
delete p;//调用析构函数撤销动态创建的Exmp1对象
return 0;
}
测试结果:
tekken@tekken:~/C++WS$ ./a.out
Exmp1()
Exmp1(const Exmp1&)
~Exmp1()
Exmp1()
Operator:(const Exmp1&)
~Exmp1()
Exmp1()
Exmp1()
Exmp1()
Exmp1()
~Exmp1()
~Exmp1()
~Exmp1()
~Exmp1()
~Exmp1()
网友评论