书中的第二章,主要讲解了 C++中的构造函数。是不是没有构造函数时,编译器都会合成一个默认的?C++的成员变量是不是和 Java 一样都初始化为0?拷贝构造函数做了哪些工作?成员初始化列表到底有没有必要?
按照 C++ standard 的描述,当没有用户申明的 constructor 时,编译器会默认的生成一个。从概念上可以这么理解,但是合成的构造函数也分 trivial 的和 nontrivial 的。一个 trivial 的构造函数,实际上什么工作也没有做,实际上生没生成,也没有太大的区别。但有一点需要记住,不管是生成的哪种构造,data members 都是不会初始化的,因为这个是程序员的工作,而不是编译器的工作。那为什么全局的、静态的对象,data members 就初始化为0呢?这个工作不是构造函数来做的,而是加载程序之时,data segment 会全部初始化为0。在以下的四种情况下,编译器会为我们生成一个 nontrivial 的构造函数,它完成一些必要的工作。
当一个 class 中包含了一个或几个带有默认构造函数的data member(不管是用户自己定义的,还是编译器生成 nontrivial 的)。则编译器必须为这个 class 生成一个默认构造函数,这个构造函数会按照 data member 的声明顺序,一次调用其默认构造函数。不仅如此,这个 class 所有的 constructors 都会调用这些 data member 的构造函数,这个过程发生在显示的用户代码之前。
当一个 class 的基类带有默认构造函数,编译器也会为它合成一个默认构造函数,在用户代码之前调用其基类的默认构造函数。当有多个 base class 都带有默认构造函数时,会按照继承列表,依次调用其默认构造函数(有时继承列表的顺序会被编译器优化)。这个规则应用于这个类的所有构造函数。
当一个类带有一个或多个虚函数时,编译器会为它合成一个默认构造函数。这个构造函数会给类的 vfptr 赋值,使其指向正确的vftable(虚函数表)。在构造一个对象的时候,分为两步。第一步是分配一块内存(栈或堆),此时内存中的数据是无意义的,包括 vfptr。第二步则是在此内存之上,调用对象的构造函数。编译器虽然不为我们初始化data member,但是 vfptr 却必须由其正确的指定。每一个带有虚函数的类,编译器都会生成一个 vftable,里面包含了正确的虚函数地址。构造函数之中,必须要将vftable 的值填入 vfptr 当中。
当一个类中带有虚基类(直接或简洁),对象中会有一个 vbptr。与 vfptr 同理,每一个类都有一个 vbtable,其中存放了共享虚基类在对象内存中的偏移量(每一个 slot 存放一个虚基类的偏移量)。构造函数需要将类的vbptr 设置为正确的 vbtable 值。
上面四种情况,编译器会为我们生成一个 nontrival 的默认构造函数,每一种情况都会合成一定的操作,这些操作之间带有严格的顺序。在分配了对象的内存之后,其构造函数执行操作的顺序如下:
不管有没有用户定义的构造函数,编译器都会为我们合成一个拷贝构造函数(用户自定义了拷贝构造函数除外),以支持用一个对象来初始化另一个对象。拷贝构造函数的参数通常都为其类型的 const 引用(以支持左值和转换)。拷贝构造函数的调用会发生在以下三种情况:
拷贝构造函数也会根据类的定义分为两种类型。如果类的定义展现了“bitwise copy semanitcs”,则逐次拷贝其内容。这种情况下,有可能并没有实际生成并调用拷贝构造函数,而是使用 memcpy 即可。在以下几种情况,则不展现“bitwise copy semanitcs”:
按照声明次序,依次拷贝 data members。带有拷贝构造函数的成员,调用其拷贝构造函数;其它成员使用赋值操作。
按照继承列表,一次调用基类的拷贝构造函数。
因为 C++的对象不展现多态性(只有使用指针或引用,才展现多态性)。所以在拷贝一个对象(有可能是派生类的对象)时,需要将其 vfptr 设置为正确的 vftable 值。
如果类带有虚基类,则拷贝它时(可能来自于派生类的对象),需要按照参数对象来初始化共享虚基类的对象,并且设置正确的 vbptr 的值。
初始化成员列表是 C++特有的(相对于 Java)。有可能会觉得它没必要,因为在构造函数里的用户代码一样可以初始化data member,例如:
class X { public: X() { s = "a"; i = 0; } X() : i(0), s("a") {} private: string s; int i; };
这两种初始化方式有区别吗?对于 i 来说是没有的,但是对于 s 就不一样了。按照之前默认构造函数的第一种场景,编译器会在用户代码之前初始化带有默认构造函数的 data member,所以其实编译器已经默默的构造了 s(默认值为"")。用户代码 s = "a" ,实际上是调用 string(const char *)生成一个临时的 string,然后再调用其拷贝构造函数,将临时的 string 赋值(不是拷贝构造)给 s 。成员初始化列表实际上就是告诉编译器,改怎么样去生成用户代码之前的构造操作。这些操作是编译器合成的,在用户代码之前,所以只能通过成员初始化列表来操作。并且有一些情况下,是必须通过成员初始化列表,而不是用户代码来操作的:
《C++对象模型》读后感——构造函数语义学,布布扣,bubuko.com
原文:http://www.cnblogs.com/xien7/p/3592738.html