当我们定义类指针或引用时到底发生了什么
在《C++对象模型——内存布局》中,我解释了C++对象在不同情境下的几种内存布局.
而我在读《Java核心技术·卷一》的时候看到了这样的一段话:
不能将一个超类的引用赋给子类变量
这本书中,是通过一个员工类作为基类派生出boss类来讲解的.代码示例如下:
1 2 3 4
| Manager boss = new Manager(); Employee staff = new Employee(); Employee e = boss; //正确 Manager m = staff; //错误
|
书中提到
不是所有的雇员都是经理。如果赋值成功,m有可能引用了一个不是经理的Employee对象,当在后面调用 m.setBonus(...)时就有可能发生运行时错误.
如果根据C++对象的内存布局来看,子类的对象往往是要比父类对象大的,如果用大的来模拟小的(切片),那么会导致越界,反之,用小的来模拟大的(切片),自然就可以了.
这样解释固然很清楚,但我认为从内存分配的角度来理解这个问题会更好,这里先从C++的多态讲起,简单的来说,C++的多态就是通过父指针指向子类对象+虚函数来实现.当父类指针指向子类对象,可以调用子类从父类继承来的那部分.但如果父类中的函数声明了virtual,则会调用子类中重写的那个函数,这也就是实现了动态绑定,也就是多态.
所以定义父类指针指向对象时是将该对象的地址赋给指针,然后根据指针类型的大小限定了访问范围,可以看作是对该对象内存分布的一个切片,只能访问该范围内的成员,也就是从父类继承过来的成员.由此看来C++对象内存分布的设计真是相当的精妙呢.
程序验证:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39
| #include <iostream>
class A { public: A(){} private: int a1; int a2; }; class B : public A { public: B(){} private: int b1; int b2; };
int main(int argc, const char * argv[]) { A a; B b; A* Aptra = &a; A* Aptrb = &b; B* Bptrb = &b; std::cout << Aptra << " " << Aptrb << " " << Bptrb << std::endl << std::endl; std::cout << sizeof(a) << " " << sizeof(b) << std::endl; std::cout << sizeof(*Aptra) << " " << sizeof(*Aptrb) << " " << sizeof(*Bptrb) << std::endl; return 0; }
输出结果:
0x7ffeefbff558 0x7ffeefbff548 0x7ffeefbff548
8 16 8 8 16
|
script>