当我们在编写代码时,写下了一个空类,那么这个空类什么时候就不再是一个空类了呢?
当编译器处理过之后,如果你自己没有声明,编译器就会为他声明一个copy构造函数、一个赋值操作符和一个析构函数。如果你没有声明任何构造函数,编译器也会为你声明一个默认构造函数。所有这些函数都是public且inline。
也就是说当你写下:
class Empty{};
//这时好像你已经写下了
class Empty{
public:
Empty(){...}
Empty(const Empty& other){...}
~Empty(){...}
//virtual见稍后说明
Empty& operator=(const Empty& other){...}
唯有这些函数被调用,它们才会被编译器创建出来,对于类来说,程序中需要他们是很平常的事:
Empty e1;//默认构造函数
Empty e3(e1);//copy构造函数
e2=e1; //赋值操作符
编译器为我们写的这些函数到底做了什么?,默认构造函数和析构函数主要是给编译器一个地方用来放置“藏身幕后”的代码,像是调用基类和非静态成员变量的构造和析构函数。注意:编译器所生成的析构函数是一个非虚析构函数,除非这个类的基类自身声明有virtual 析构函数(这种情况下这个函数的虚属性主要来自基类),这里有点绕,也就是说在基类中声明了一个虚析构函数时,派生类中的析构函数的虚属性来自于基类,换句话说,当想要覆盖派生类被析构,那么基类中要有虚析构函数(我们后续再说)。
至于copy构造函数和赋值操作符,编译器创建的版本只是单纯地将来源对象的每一个非静态成员变量拷贝到目标对象。考虑考虑一个NamedObject template,它允许你将一个个名称和类型为T的对象产生关联:
template<typename T>
class NamedObject
{
public:
NamedObject(const char* name,const T& value);
NamedObject(const std::string& name,const T& value);
...
private:
std::string nameValue;
T objectValue;
}
由于这里声明了构造函数,编译器将不再创建默认构造函数。这一点很重要,意味着你想要设计一个构造函数需要实参,就不用担心编译器给你添加一个无参构造函数了。
这里NamedObject既没有声明copy构造和赋值操作符,那么编译器就会为他创建哪些函数。现在这要这么用:
NamedObject<int >no1("hello",2);
NamedObject<int> no2(no1);//调用copy构造函数
分析一下:
编译器生成的copy构造函数必须以no1.nameValue和no1.objectValue为初值设定no2的。两者之中,nameValue的类型是string,而标准string有个copy构造函数,所以,no2.nameValue的初始化方式是调用string的copy构造并以no1.nameValue为实参。另一个类型为int,则直接拷贝每一个字节来完成初始化。
编译器为NamedObject<int>所生的赋值操作符,其行为基本上与copy构造函数如出一辙,但一般而言只有当生出的代码合法且有适当机会证明它有意义,万一两个条件有一个不符合,编译器会拒绝为class生出operator=。
举一个例子,假设NamedObject定义如下,其中nameValue是个引用string,objectValue是个const T:
template<class T>
class NamedObject{
public:
NamedObject(std::string& name,const T& value);
...
private:
std::string& nameValue;
const T objecValue;
};
std::string newDog("hehe");
std::string oldDog("haha");
NamedObject<int>p(newDog,2);
NamedObject<int>s(oldDog,36);
p=s;
赋值之前,p.nameValue,s.nameValue都指向string对象。赋值之后,应该怎么办,引用可以被改动吗?当然是不可以的,面对这种情况,编译器会拒绝赋值动作,你想要有这样的操作,就必须自己定义赋值操作符。
最后还有一种情况:如果某个基类将赋值操作符声明为private,编译器将拒绝为其派生类生成赋值操作符。毕竟编译器为派生类所生的赋值操作符想象中可以处理基类成分,但他们无法调用派生类无权调用的成员函数。编译器表示无能为力。
总结:
编译器可以暗自为类创建默认构造函数、copy构造函数、赋值操作符、以及析构函数。