《深度探索c++内存模型》读书笔记 (二)

前言

c++ 编译器会在人意想不到的地方做一些隐式操作。例如,只含有一个参数的构造函数,会被当做类型转换运算符。而关键字explict就是为了阻止这一机制。

默认构造函数

c++ 编译器会在需要的时候自动生成默认构造函数。

带有默认构造函数的类对象成员

如果一个类没有任何的构造函数,但是它有一个对象成员,这个对象成员有一个默认构造函数。那么编译器将会为这个类生成一个默认构造函数。但是这个生成的时机会是在这个类被调用的时候。
如果有两个文件中都调用了这个类,那么默认构造函数将可能会被生成两次。如何避免这种冲突呢?解决方法是把合成的构造函数内联(inline, 一个内联函数有静态链接 static linkage)。如果构造函数太复杂,不适合内联,那么就会合成一个显式的非内联静态实体(explicit noinline static)。

例如:

class Foo {
public:
	Foo()
};
class Bar {
Foo foo;
char *str;
};
// 编译器将会为Bar合成默认构造函数,初始化foo成员。但是并不会初始化str成员!!!

如果一个类Foo有自己定义的构造函数(默认的,或者带有参数的),并且这个类还有一个或者多个类成员。那么编译器将会保证这些类成员的构造函数在Foo的构造函数中至少被调用一次。这些成员的构造顺序与它们在class中的声明顺序一致。

带默认构造函数的基类

类似的,如果一个没有构造函数的派生类继承自一个带有默认构造函数的基类。那么编译器将会为这个派生类生成默认构造函数。在这个生成的构造函数中按照声明的顺序调用基类的构造函数。

如果派生类有构造函数,但是没有默认构造函数。编译器将会为现有的构造函数中安插调用基类构造函数的代码,但是并不会生成一个新的默认构造函数。

如果这个派生类同时有类成员(这些类成员有默认构造函数)。那么编译器将会在基类构造函数的代码后面安插调用数据成员构造函数的代码。

带有虚函数的类

有以下两种情况会生成默认构造函数

  • 类声明或者继承一个虚函数
  • 类派生自一个继承串链,其中有一个或者更多的虚基类(virtual base class)

在编译期间,编译器会做两件事情:

  1. 生成一个虚函数表
  2. 为每一个对象添加一个额外的虚函数表指针。

对于类所定义的每一个构造函数,编译器将会安插一些代码,为虚表指针赋初值。带有一个虚基类的类编译器会使一个虚基类在派生类中的位置 在执行期间所决定??
例如

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;};
​
// 无法在编译时期决定i的位置(偏移?)
void foo(const A *pa) {
    pa->i = 1024;
}
​
int main ()
{
    foo (new C);
    foo (new A);
    ...
}

编译器的一种可能的实现方式为:
在每一个派生类的虚基类中安插一个指针。经由引用或者指针来存取一个虚基类的功能都可以由这个指针来实现。
例如:

// 可能的转换操作
void foo(const A *pa) {
    
    
    pa->__vbcX->i = 1024;
}

总结

在编译器合成的默认构造函数中,会为那些有默认构造函数的成员或者基类调用其默认构造函数。而那些其他的非静态数据成员,例如整数,指针等都不会被初始化,需要类的设计者(就是程序员了)来做这些操作。

新手一般会有两个误解

  1. 任何类如果没有定义默认构造函数,那么就会被合成出一个来。
  2. 编译器合成的默认构造函数会明确设置类中的每一个数据成员初始值。

这两个没有一个是真的!!!

猜你喜欢

转载自blog.csdn.net/weixin_49265931/article/details/109137968