内容简介:
在C++中,编译器自动会为一个类创建一个default构造函数、一个copy构造函数、一个赋值运算符函数(operator=),以及一个析构函数。本节主要看构造函数和析构函数在实际使用过程中的一些注意事项。
构造函数和析构函数使用注意事项:
1). 构造函数和析构函数都不能用const来修饰,因为在初始化和释放时,它们总是对对象作些修改;
代码示例:
class Base {
public:
int a;
const Base(){cout<<"Base constuctor"<<endl;}//error: constructor cannot have a return type
const virtual void fun1() {cout<<"Base::fun1"<<endl;} //success
virtual void fun2() {cout<<"Base::fun2"<<endl;}
virtual void fun3() {cout<<"Base::fun3"<<endl;}
~Base(){cout<<"Base destroy"<<endl;}
};
int main () {
Base b;
return 0;
}
以上代码编译发生错误,错误信息如下:
error: constructor cannot have a return type
const Base(){cout<<"Base constuctor"<<endl;}
^~~~~~
可见,提示信息也明确说明,构造函数的返回类型是错误的,主要是因为在构造函数前用了const。
正确的方式应该是:
Base(){cout<<"Base constuctor"<<endl;}
2). 构造函数、析构函数和赋值函数(operator=)不能被继承。
3). 不要在构造函数和析构函数中调用虚函数(virtual function)。
在构造和析构期间不要调用虚函数,因为这类调用从不下降至派生类。由于基类构造函数的执行更早于派生类构造函数,当基类构造函数执行时派生类的成员变量尚未初始化,如果此期间调用的虚函数下降至派生类阶层,因为派生类的函数几乎都会调用派生类的成员变量,又因为这些成员变量尚未初始化。要求使用对象内部尚未初始化的成分是危险的代名词,所以C++不允许这样做。
看下面这个例子:
#include <iostream>
using namespace std;
class Base {
public:
int a;
Base(){
cout<<"Base constuctor"<<endl;
fun2();
}
void callFun() {fun2();}
const virtual void fun1() {cout<<"Base::fun1"<<endl;} //success
virtual void fun2() {cout<<"Base::fun2"<<endl;}
virtual void fun3() {cout<<"Base::fun3"<<endl;}
~Base(){cout<<"Base destroy"<<endl;}
};
class Derive : public Base{
public:
int b;
Derive(){
cout<<"Derive constuctor"<<endl;
fun2();
}
virtual void fun2() {
cout<<"Derive::fun2"<<endl;
}
~Derive(){cout<<"Derive destroy"<<endl;}
};
int main () {
Derive b; //在父类中的构造函数中,调用了父类的fun2
b.callFun(); //在父类中的callFun中,调用了子类的fun2,从而引起二义性。
return 0;
}
运行结果:
Base constuctor
Base::fun2
Derive constuctor
Derive::fun2
Derive::fun2
Derive destroy
Base destroy
分析说明:
1)callFun是基类Base的成员函数,它调用了func2函数;
2)func2是基类中的虚函数,它被Base类的构造函数调用;
期望的结果:
func2函数是虚函数,我们期望在使用子类的实例(Derive b)调用到func2时,一定会调用到子类的func2函数。
但是,在上述代码中,func2却被父类调用了(在父类的构造函数中调用的),这是我们部期望看到的。
在构造和析构期间不要调用虚函数,因为这类调用从不下降至派生类。由于基类构造函数的执行更早于派生类构造函数,当基类构造函数执行时派生类的成员变量尚未初始化,如果此期间调用的虚函数下降至派生类阶层,因为派生类的函数几乎都会调用派生类的成员变量,但此时成员变量尚未初始化。
4). 别让异常逃离析构函数:
析构函数绝对不要吐出异常。虽然在语法上,C++并不禁止析构函数吐出异常,但不鼓励你这样做。因为析构函数是一个实例的消亡过程,在实例消亡时,一切都已结束,而抛出异常异味着还有未结束的事情,就会产生二义性,到底是结束还是未结束。
所以,如果一个析构函数调用的函数可能抛出异常,析构函数应该捕捉任何异常,然后处里他们或结束程序;
如果客户需要对某个操作函数运行期间抛出的异常做出反应,那么class应该提供一个普通函数(非析构函数)执行该操作。
5).编译器产生的析构函数是非虚函数,除非这个类的父类有一个虚析构函数。
6). 为多态基类声明虚析构函数
带有多态性质的基类应该声明一个虚析构函数,即如果一个类带有任何虚函数,它就应该拥有一个虚析构函数;
类的设计目的如果不是作为基类使用,或者不是为了实现多态,就不应该声明析构函数为虚函数。
C++指出,当派生类对象经由一个基类指针被删除,而该基类有一个非虚析构,那么结果是未定义的。实际上执行时通常发生的是对象的derived成员没有被销毁(派生类的析构函数没有被调用),但是其基类成员被销毁了,造成了资源泄漏。
所以,给基类一个虚析构函数,那么以后通过基类指针或者引用删除该派生类的时候,就会销毁整个对象。
示例代码:
#include <iostream>
using namespace std;
class Base {
public:
int a;
Base(){
cout<<"Base constuctor"<<endl;
}
void callFun() {fun2();}
const virtual void fun1() {cout<<"Base::fun1"<<endl;} //success
virtual void fun2() {cout<<"Base::fun2"<<endl;}
virtual void fun3() {cout<<"Base::fun3"<<endl;}
virtual ~Base(){cout<<"Base destroy"<<endl;}
};
class Derive : public Base{
public:
int b;
Derive(){
cout<<"Derive constuctor"<<endl;
fun2();
}
virtual void fun2() {
cout<<"Derive::fun2"<<endl;
}
~Derive(){cout<<"Derive destroy"<<endl;}
};
int main () {
Derive b;
b.callFun();
return 0;
}
运行结果:
Base constuctor
Derive constuctor
Derive::fun2
Derive::fun2
Derive destroy
Base destroy
可见,父类的析构函数(打印Base destroy)总是能够被正确执行。
确保一个实例对象能够正确被创建和销毁。