深入理解C++数据成员的继承
最近在工作方面遇到了这样的问题,即所有的子类都必须拥有一个数据成员,为了对接口进行约束,最好的办法就是将该数据成员放在基类中并使用protected修饰。首先用代码来描述我的问题:
class BaseB;
class BaseA
{
Base();
virtual ~Base();
//...other operation
protected:
BaseB *m_p;
}
其实说起来很简单,即BaseA,BaseB都是基类,BaseA中有一个BaseB的指针,因为在以后的子类中,需要使用道BaseB的多态特性。
跳出问题的本身,C++中,几乎所有的C++特性例如多态都是通过虚函数表来实现的。
那么数据成员的继承是如何实现的呢?类与类对象的内存存储情况又是怎样的呢?为了阐述这个问题,我们还是先来看一段小代码:
#include <stdio.h>
#include <iostream>
using namespace std;
class Base
{
public:
Base(){}
void operation(){}
protected:
char a;
int b;
};
class A: public Base
{
public:
short c;
};
int main(int argc, char **argv)
{
A obj;
cout << "the size of the obj is " << sizeof(obj) << endl;
}
输出结果为:the size of the obj is 12
分析这个12个字节很简单:1+3+4+2+2 = 12
其实就是
struct _data
{
char a;
int b;
short c;
}
结构默认4字节内存对其后的大小。
这段代码至少说明了2点:
1. 非虚函数不占用类对象空间,这一点很好解释,非虚函数不是run time,也就意味着在编译阶段,函数地址已经知道,编译过程中编译器已经将函数调用的地方替换成了该函数入口地址。成员函数没有存放在类中。再有虚函数的情况下,只占用了4个字节的虚函数表的指针。这也就是为什么虚函数会是类空间增大。
2. 派生类中复制了一份基类继承下来的数据。C++中,对于数据成员的继承其实就是将基类继承的数据拷贝了一份到派生类中。这一点从另一方面来说,class 完全可以替换struct而不增加对象大小。这一点,包括我自己,有时候在C++中写数据结构的时候,习惯性的使用struct,而避免使用class,总是觉得class会使得占用的空间增大。这是一个误区
上面的一小段代码中,派生类A拥有的数据成员有:a, b, c.成员方法有 void operation.其类对象内存分布如下图:
_____________________________________
|__________char a____________________ _|
|__________int b_______________________|
|__________short c_____________________|
可见,派生类对象中,只有数据成员,并且复制了一份基类的继承下来的数据成员。
说道这里,基本已经知道了继承数据成员是如何实现的。
接下来继续看:
#include <stdio.h>
#include <iostream>
using namespace std;
class Base
{
public:
Base(){ a = 0; b = 0;}
virtual void operation(){ b = 12;}
virtual void print(){}
protected:
char a;
int b;
};
class A: public Base
{
public:
short c;
void operation() { b = 24; }
void print() { cout << "result is " << b << endl;}
};
int main(int argc, char **argv)
{
A obj;
Base *p = &obj;
//cout << "the size of the obj is " << sizeof(obj) << endl;
p->Base::operation();//注意:这里是强行调用基类的方法
p->print();//使用调用的事派生类的print
p->operation();//调用派生类A的方法
p->print();
}
结果是:
result is 12
result is 24
Base::operation()强行调用基类的方法对数据成员进行修改。在子类中通过打印发现,子类中从基类继承下来的数据也发生了变化。在实现数据成员继承的过程中,虽然是拷贝了基类的继承下来的数据成员,但是,基类和子类中该数据成员只有一个实例,也就意味着在基类和子类中继承的数据成员公用一份。