如何正确领略C++默认结构函数
如何正确领略C++默认结构函数
副标题#e#
对付C++默认结构函数,我曾经有两点误解:
类假如没有界说任何的结构函数,那么编译器(必然会!)将为类界说一个合成的默认结构函数。
合成默认结构函数会初始化类中所有的数据成员。
第一个误解来自于我进修C++的第一本书 《C++ Primer》,在书中392页:“只有当一个类没有界说结构函数时,编译器才会自动生成一个默认结构函数”。
实际上这句话也没有说错,它说明白默认结构函数界说的须要非充实条件,然而却给其时初学C++的我造成了必然的误解。
第二个误解依旧来自于Primer中的一句话:“合成的默认结构函数利用与变量初始化沟通的法则来初始化成员。具有类范例的成员通过运行各自的默认结构函数来举办初始化”。然而这也是我领略的单方面,因为Primer也说到了:“假如类包括内置或复合范例的成员,则该类不该该依赖于合成的默认结构函数”,言下之意就是合成的默认结构函数并不会初始化内置或复合范例的成员。
总结了我有这些误解的原因,第一是初学时常识体系没形成,对Primer中所说的内容没有真正的领略,第二就是Primer在某种水平上简直不是C++初学者能看懂的书,或者看时以为懂了,却是漏掉了许多常识。也说明白Primer 是座宝库,经常回首将会有新的感悟。
让我对上面两个概念发生迷惑,是在看《Effective C++》时,条款05《相识C++默认编写并挪用哪些函数》中说到“….惟有当这些函数被需要(被挪用),它们才会被编译器建设出来。” (“这些函数“指的是编译器版本的复制结构函数、赋值操纵符和析构函数,还包罗了默认结构函数。)也就是说,默认结构函数“被需要”的时候编译器才会帮我们合成,那什么环境才是默认结构函数”被需要“呢?这个问题《Effective C++》并没有给出谜底,直到看了《深度摸索C++工具模子》,才大白了编译器何时才会帮我们合成一个默认结构函数。
我写这篇文章的目标是给和我有同样误解或迷惑的C++初学者看的,假如你对合成默认结构函数已有充实的认识,请忽略本文的内容。
正文
什么是默认结构函数?
默认结构函数是可以不消实参举办挪用的结构函数,它包罗了以下两种环境:
没有带明明形参的结构函数。
提供了默认实参的结构函数。
类设计者可以本身写一个默认结构函数。编译器帮我们写的默认结构函数,称为“合成的默认结构函数”。强调“没有带明明形参”的原因是,编译器老是会为我们的结构函数形参表插入一个隐含的this指针,所以”本质上”是没有不带形参的结构函数的,只有不带明明形参的结构函数,它就是默认结构函数。
默认结构函数什么时候被挪用?
假如界说一个工具时没有提供初始化式,就利用默认结构函数。譬喻:
class A { public: A(bool _isTrue= true, int _num=10){ isTrue = isTrue; num = _num; }; //默认结构函数 bool isTrue; int num; }; int main() { A a; //挪用类A的默认结构函数 }
领略“被需要”这三个字
前面提到在《Effective C++》中指出惟有默认结构函数”被需要“的时候编译器才汇合成默认结构函数。要害字眼是”被需要“。被谁需要?做什么工作?像下面这段代码,默认结构函数”被需要“了吗?
class A { public: bool isTrue; int num; }; int main() { A a; if (a.isTrue) cout << a.num; return 0; }
你大概认为这里界说类工具a的时候没有提供参数且A没有界说默认结构函数,编译器必定是合成了一个默认结构函数并挪用它来初始化A的数据成员,实则不是。当你试图查察合成默认结构函数把数据成员num初始化为什么值的时候,你会发明编译器甚至都让你运行不了措施:
当类只含有内置范例或复合范例的成员时,编译器是不会为类合成默认结构函数的,这种类并不切合”被需要“的条件,甚至当类满意“被需要”条件,编译器合成了默认结构函数时,类中内置范例与复合范例数据成员依然不会在默认结构函数中举办初始化。Primer中也有提到:“假如类包括内置或复合范例的成员,则该类不该该依赖于合成的默认结构函数“。
上面代码中,默认结构函数”被需要“是对措施来说的,措施需要isTrue被初始化以便可以举办条件判定,需要num被初始化以便可以输出。然而这种需要并不会促使编译器合成默认结构函数。惟有被编译器所需要时,编译器才汇合成默认结构函数。那奈何的类才是编译器需要合成默认结构函数的呢?
总结:
合成默认结构函数老是不会初始化类的内置范例及复合范例的数据成员。
#p#分页标题#e#
分清楚默认结构函数被措施需要与被编译器需要,只有被编译器需要的默认结构函数,编译器才汇合成它。
#p#副标题#e#
何时默认结构函数才会被编译器需要?
以下四种环境的类,编译器老是需要默认结构函数完成某些事情:
1. 含有类工具数据成员,该类工具范例有默认结构函数。
假如一个类没有任何结构函数,可是它含有一个类工具数据成员,且该类工具范例有默认结构函数,那么编译器就会为该类合成一个默认结构函数,不外这个合成操纵只有在结构函数真正需要被挪用的时候才会产生。举个例子,编译器将为类B合成一个默认结构函数:
class A { public: A(bool _isTrue=true, int _num = 0){ isTrue = _isTrue; num = _num; }; //默认结构函数 bool isTrue; int num; }; class B { public: A a;//类A含有默认结构函数 int b; //... }; int main() { B b; //编译至此时,编译器将为B合成默认结构函数 return 0; }
被合成的默认结构函数做了什么工作?或许如下面这样:
B::B() { a.A::A(); }
被合成的默认结构函数内只含须要的代码,它完成了对数据成员a的初始化,但不发生任何代码来初始化B::b。正如上面所说,初始化类的内置范例或复合范例成员是措施的责任而不是编译器的责任。为了满意措施的需要,我们一般会本身写结构函数来对B::b举办初始化,像这样:
B::B() { a.A::A(); //编译器插入的代码 b = 0; //显示界说的代码 }
假如类中有多种类工具成员,则编译器凭据这些类工具成员声明的顺序,在结构函数按顺序插入挪用各个类默认结构函数的代码。
2.基类带有默认结构函数的派生类。
当一个类派生自一个含有默认结构函数的基类时,该类也切合编译器需要合成默认结构函数的条件。编译器合成的默认结构函数将按照基类声明顺序挪用上层的基类默认结构函数。同样的原理,假如设计者界说了多个结构函数,编译器将不会从头界说一个合成默认结构函数,而是把合成默认结构函数的内容插入到每一个结构函数中去。
3. 带有虚函数的类
类带有虚函数可以分为两种环境:
类自己界说了本身的虚函数
类从担任体系中担任了虚函数(成员函数一旦被声明为虚函数,担任不会改变虚函数的”虚性质“)。
这两种环境都使一个类成为带有虚函数的类。这样的类也满意编译器需要合成默认结构函数的类,原因是含有虚函数的类工具都含有一个虚表指针vptr,编译器需要对vptr配置初值以满意虚函数机制的正确运行,编译器会把这个配置初值的操纵放在默认结构函数中。假如设计者没有界说任何一个默认结构函数,则编译器汇合成一个默认结构函数完成上述操纵,不然,编译器将在每一个结构函数中插入代码来完成沟通的工作。
4.带有虚基类的类
虚基类的观念是存在于类与类之间的,是一种相对的观念。譬喻类A虚担任于类X,则对付A来说,类X是类A的虚基类,而不能说类X就是一个虚基类。虚基类是为了办理多重担任下确保子类工具中每个父类只含有一个副本的问题,好比菱形担任。如下图:
于是,类A工具中含有一份类X工具,类C中也含有一份类X工具,当我们赶上如下代码时:
class X { public: int i; }; class A : public virtual X{ public:int j; }; class B : public virtual X{ public:double d; }; class C : public A, public B{ public: int k; }; void function(A *pa) { pa->i = 1000; } int main() { A *a= new A(); C *c= new C(); function(a); //存眷重点在这里 function(c); //存眷重点在这里 return 0; }
函数function参数pa的真正范例是可以改变的,既可以把A工具指针赋值给pa,也可以把工具指针赋值给pa,在编译阶段并无法确定pa存储的i是属于A照旧C的虚基类工具。为了办理这问题,编译器将发生一个指向虚基类X的指针,使得措施得以在运行期确定经过pa而存取的X::i的实际存储位置。这个指针的安插,编译器将会在合成默认结构函数中完成,同样的,假如设计者已经写了多个结构函数,那么编译器不会从头写默认结构函数,而是把虚基类指针的安插代码插入已有的结构函数中。
总结
从头强调文章开篇所提,以下两个概念都是误解:
a) 任何类假如没有界说结构函数,则编译器会帮我们合成一个默认结构函数。
b) 合成默认结构函数会对类中的每一个数据成员举办初始化。
#p#分页标题#e#
只有在编译器需要默认结构函数来完成编译任务的时候,编译器才会为没有任何结构函数的类合成一个默认结构函数,可能是把这些操纵插入到已有的结构函数中去。
编译器需要默认结构函数的四种环境,总结起来就是:
a) 挪用工具成员或基类的默认结构函数。
b) 为工具初始化虚表指针与虚基类指针。
PS:假如本文哪个处所叙述不清楚可能错误,十分等候指出,多谢!
来历:http://www.cnblogs.com/QG-whz/p/4676481.html