构造、析构函数的由来:
由于C++类是许多成员函数和成员变量的集合,在实例化一个类时肯定需要将其内部变量初始化,而这个工作如果由程序员在创建对象后显示调用初始化函数完成,恐怕难免会有疏漏,所以C++之父发挥程序员“”懒惰“的特性,规定了两个函数分别是构造和析构函数,来完成初始化和清理工作,而这两个函数很重要的一点是,他们是在对象创建和消亡时自动执行的。
特别的是,如果程序员没有定义构造和析构函数,编译器会自动生成,但这只是需有其表而已,有时还有带来错误,例如自动生成的拷贝构造函数,是按位拷贝的,当有指针变量时,就会带来问题,所以建议还是主动定义构成和析构函数,而像拷贝构造函数和赋值函数,不需要使用最好主动禁止它们,方法是在private权限下只声明而不定义它们。
class String
{
public:
String(const char *str = NULL);
String(const String &other);
~String();
String& operator=(const String &other);
private:
char *m_data;
};
String::String(const char *str)
{
if (NULL == str)
{
m_data = new char[1];
*m_data = '\0';
}
else
{
m_data = new char[strlen(str)+1];
strcpy(m_data, str);
}
}
String::String(const String &other)
{
m_data = new char[strlen(other.m_data)+1];
strcpy(m_data, other.m_data);
}
String::~String()
{
delete[] m_data;
/*
delete m_data;
*/
}
String& String::operator =(const String &other)
{
String tmp(other);
char *orig = m_data;
m_data = tmp.m_data;
tmp.m_data = orig;
return *this;
/*
if (this == other)
return *this;
delete[] m_data;
m_data = new char[strlen(other.m_data)+1];
strcpy(m_data, other.m_data);
return *this;
*/
}
以上代码是String类的简单实现,从中我们可以总结几点:
1、构造函数和析构函数的函数名必须与类名相同,保证编译器能明确识别。而且都不能有返回值,这也许是由于它们在对象创建和消亡时自动执行,没必要返回任何值,又或许是因为没有返回值显得比较另类,以突出它们的特殊性吧。注意赋值函数不是构造函数,从返回值这一点就能看出。
2、构造函数可以有多个,包含一个拷贝构造函数,以提供灵活的对象初始化方式,而这也是重载存在的必要性。但析构函数不能有多个,不然编译器无法确定使用哪个。
3、拷贝构造函数的参数需要为引用类型,如果是传值,那么在实参拷贝到形参时又会调用拷贝构造函数,也就是发生无限递归,这会导致栈溢出,所以C++编译器会直接禁止参数为传值形式的拷贝构造函数声明。
4、赋值函数(针对上述代码):
1)由于原来对象中的申请的空间不一定能满足拷贝需求,所以需要重新申请,但不能简单的释放资源后再申请资源然后拷贝内容。这样存在两个问题,自我赋值和异常安全性。当赋值的两个对象为同一个时,释放了源对象资源也就是释放了被拷贝对象的资源,之后再去拷贝内容时,拷贝的是一段已释放的空间;还有一个问题是如果new执行失败,而对象里的资源已经被释放,那么该对象中相当于保存了一个野指针。
2)处理这两个问题的关键在于不能提前释放对象里的资源,可以向对象里的指针指向新的地址,然后将指向地址里的空间释放,而上述代码里设计更为巧妙,先创建一个局部对象,将内容拷贝到这个局部对象,再互换原对象和局部对象的空间资源,当函数退出后,局部对象会调用析构函数释放空间,而这段空间恰好是原对象的最初那段空间。这样处理后同样也解决了自我赋值的问题。
3)关于new和delete操作符,当new失败时,它并不会返回NULL(可能老版本为了兼容C是这样处理的),而是会抛出异常,而delete如果new时用的是[],delete也需要用[].
参考文献:《高质量C++编程指南》
6、由于构造、析构函数不能够继承,所以派生类的构造函数应该在初始化列表中调用基类的构造函数;
7、基类和派生类的构造函数应该为虚(virtual);
8、构造和析构的次序:
构造函数遵循从内向外构造的次序,即先构造基类,再派生类,然后一层层向外的次序。
析构函数正好想法,即先析构最外层,逐渐向内层析构。