如果这篇博客帮助到你,可以请我喝一杯咖啡~
CC BY 4.0 (除特别声明或转载文章外)
一 class layout
不影响 class layout
:
static member
具有静态的作用域(static lifetime
) 以及 线程的存储期(thread storage duration
)。type member
(包括嵌套类型)non-virtual member functions
0. Empty Base Class Optimize
class Empty0 {};
class Empty {};
class Empty2
{
Empty e1;
Empty e2;
Empty e3;
};
class I
{
int i;
};
class Empty3 : public Empty0, public Empty
{
int a;
};
int main()
{
Empty e1;
Empty2 e2;
Empty3 e3;
cout << sizeof e1 << " " << sizeof e2 << " " << sizeof e3 << endl;
}
1 3 4
1. 多态会修改指针的值!
derived -> base
编译时绑定
将 derieve class ptr
赋给 base class ptr
时, ptr
的值会根据 base object
在 derieve class
中的 layout
进行偏移
class base1
{
public:
virtual void func1() {}
int i;
};
class base2
{
public:
virtual void func1() {}
};
class derieve1 : public base1, public base2
{
public:
void func1() override {}
};
int main()
{
derieve1* d1 = new derieve1;
base1* b1 = d1;
base2* b2 = d1;
cout << b1 << " " << b2 << endl;
}
输出:
[global operator new] size = 24
0x18cc27918e0 0x18cc27918f0
如果涉及到了 virtual inheritance
这个现象会更加明显( virtual
继承下,如果基类和派生类没有 data,编译器会优化 ):
class base1
{
public:
virtual void func1() {}
int i;
};
class derieve2 : virtual public base1
{
public:
int x;
};
class derieve3 : virtual public base1
{
public:
};
class derieve4 : public derieve2, public derieve3
{
int i;
};
int main()
{
derieve4* d3 = new derieve4;
base1* b4 = d3;
derieve2* d4 = d3;
derieve3* d5 = d3;
cout << d3 << endl;
cout << d4 << " " << d5 << " " << b4 << endl;
}
输出为:
[global operator new] size = 48
0x18cc2791a60
0x18cc2791a60 0x18cc2791a70 0x18cc2791a80
base -> derived
运行时绑定
将 base class ptr
通过 dynamic_cast
转回 derieved class
也会改变指针的值:
derieve2* d6 = new derieve2;
base1* b5 = d6;
cout << d6 << " " << b5 << endl;
derieve2* d7 = dynamic_cast<derieve2*>(b5);
cout << d7 << endl;
输出为:
[global operator new] size = 32
0x18cc2791aa0 0x18cc2791ab0
0x18cc2791aa0
2. 同一个对象可以有多个合法地址(多继承)
一个类对象的指针可以与其所有父类的指针在 ==
的比较下相同
实际上是子类对象指针在尝试想父类进行 static_cast
derieve1* d1 = new derieve1;
base1* b1 = d1;
base2* b2 = d1;
if(d1 == b2) cout << "d1 == b2" << endl;
if(d1 == b1) cout << "d1 == b1" << endl;
//if(b1 == b2) cout << "b1 == b2" << endl; // error
cout << b1 << " " << b2 << endl;
[global operator new] size = 24
d1 == b2
d1 == b1
0x1c8d9bd18e0 0x1c8d9bd18f0
3. 单继承下同一个对象也可能有多个地址!
using std::cout;
class Fruit
{
public:
Fruit() { }
void whoAmI() { cout << "Fruit\n"; }
void whoAmIReally() { cout << "Fruit\n"; }
~Fruit() = default ;
};
class Apple : public Fruit
{
public:
Apple() { }
void whoAmI() { cout << "Apple\n"; }
virtual void whoAmIReally() { cout << "Apple\n"; }
virtual ~Apple() = default ;
};
int main()
{
std::shared_ptr<Apple> e1 = std::make_shared<Apple>();
std::shared_ptr<Fruit> e2 = e1;
std::shared_ptr<Apple> e3 = std::dynamic_pointer_cast<std::shared_ptr<Apple>>(e2); // error
}
这种情况下 base class
是不能 dynamic_cast
到 derived class
的,因为 base class
没有 vptr
4. 虚拟继承和多继承下 查找 this
指针的问题
b2p
指向 base class B2
在 class D
的内存模型中的位置,当其需要调用 D
重写的函数时,需要知道 D
的 this
指针。
使用 immediate data ?
delta
是一条计算好的指令
查虚表?
这种方案可行,但是我认为编译器不会采取这个方案。因为从 derived -> base
这是 static_cast
也就是编译时完成的,所以所有 base class subobject
到 complete object
的偏移 delta
都是编译器知道的,不用差虚表也可以完成。
二 dynamic_cast
0. 虚表总是被 MDO
控制
- 虚表整体(schema)被对象的静态类型控制
- 虚表中的数据由对象(MDO)的动态类型控制
2. dynamic_cast
三个使用场景
Ⅰ. dynamic_cast
to most-derived class
用 void*
来表示 MDO
示例代码:
Ⅱ dynamic_cast
to sibling base
- 首先转为 MDO,查表,找到
typeid
- 如果
typeid(mdo) == source type
,返回null
- 否则调用
castToBase
Ⅲ dynamic_cast
from base to derived
原理同 2
注意:如果是将 derived class ptr
转为 base class ptr
使用的是 dynamic_cast
,实际上等同于 static_cast
,这是编译期完成的类型转换,不是运行时绑定的。
Ⅳ. 一个例子
Itanum
和 MSVC
会产生只读的数据,根据类的继承体系建图,通过图算法查看 A 和 B 是否联通。
3. 无二义且可访问
三 C++ object model
An object is a region of storage
address order
pod
inheritance
name mangling
- 函数形参增加 this 指针
- 将函数末尾
cv
限定符移动到形参上 - 实参加上
this->
- 修改函数名称
c++filt
class with non-static function
类中的函数并不代表类中存在一个指向该函数的指针。按照 C++ 的 0开销原则,与其让大量对象的大量指针指向相同的函数,不如让编译器去决定该调用那个函数。
virtual function
使用智能指针!
using std::cout;
class Fruit
{
public:
void whoAmI() { cout << "Fruit\n"; }
virtual void whoAmIReally() { cout << "Fruit\n"; }
virtual ~Fruit() = default ;
};
class Apple : public Fruit
{
public:
void whoAmI() { cout << "Apple\n"; }
void whoAmIReally() override { cout << "Apple\n"; }
~Apple() = default ;
};
int main()
{
std::unique_ptr<Fruit> e1 = std::make_unique<Apple>();
e1->whoAmI();
e1->whoAmIReally();
}
输出:
[global operator new] size = 8
Fruit
Apple
[global operator delete] ptr = 0x25c6a2018e0
vtable 中的值由谁设置?
虚函数表在数据段,虚函数在代码段。
构造函数 在运行期初始化 vtable
构造函数为你做的工作
对于 Complex
类:
- 在对象的最前面插入一个
vptr
- 在构造函数初始化列表执行前中初始化
vptr
对于 Derived
类:
- 调用父类构造函数
- 修改
vptr
的值
思考下面的问题:
当父类构造函数中的 whoAmIReally
被调用时,vptr
指向的还是 Fruit
类的虚表!没不是 Apple
类的虚表。
可以这样理解:
当调用基类的构造函数时,
vptr
可以看作是基类类型的
using std::cout;
class Fruit
{
public:
Fruit() { whoAmIReally(); }
void whoAmI() { cout << "Fruit\n"; }
virtual void whoAmIReally() { cout << "Fruit\n"; }
virtual ~Fruit() = default ;
};
class Apple : public Fruit
{
public:
Apple() { }
void whoAmI() { cout << "Apple\n"; }
void whoAmIReally() override { cout << "Apple\n"; }
~Apple() = default ;
};
int main()
{
Apple e1;
}
override
很关键
尤其是在使用库函数时,如果不写 override
,那么会改变函数标签!你就在写一个全新的函数!