面试题答案
一键面试1. C++ 单继承体系下对象模型和内存布局
- 对象模型:
- 非虚继承:在单继承非虚函数的情况下,派生类对象包含基类对象的所有成员(数据成员和成员函数),并且在内存中派生类对象的基类部分先存储,然后是派生类新增的成员。例如:
class Base {
public:
int baseData;
};
class Derived : public Base {
public:
int derivedData;
};
对于 Derived
对象,内存布局先是 baseData
,接着是 derivedData
。
- 虚函数情况:当基类有虚函数时,基类对象会有一个虚函数表指针(vptr),通常放在对象内存布局的开头。这个指针指向一个虚函数表(vtable),vtable 中存储了虚函数的地址。派生类如果重写了虚函数,vtable 中对应虚函数的地址会被替换为派生类的虚函数地址。例如:
class Base {
public:
virtual void virtualFunc() {}
int baseData;
};
class Derived : public Base {
public:
void virtualFunc() override {}
int derivedData;
};
对于 Derived
对象,内存布局开头是 vptr,接着是 baseData
,然后是 derivedData
。
2. 内存布局:
- 内存布局遵循一定的对齐规则。例如,不同类型的数据成员会按照其自身的对齐要求进行对齐。假设
int
是 4 字节对齐,double
是 8 字节对齐。如果一个类中有int
和double
成员,int
可能会占用 4 字节后,为了满足double
的 8 字节对齐,可能会有一些填充字节。
2. 优化内存布局以提高程序性能的思路
- 合理成员布局:
- 按大小排序成员:将大的数据成员放在小的数据成员之后,以减少填充字节。例如:
class Example {
public:
double largeData; // 8 字节
int smallData; // 4 字节,这里不需要额外填充
};
- 将频繁访问的成员放在一起:缓存是以块为单位读取内存的,将经常一起访问的成员放在相邻位置,可以提高缓存命中率。例如,如果某些成员总是在特定的函数中一起使用,就把它们放在一起。
- 继承方式优化:
- 尽量避免不必要的虚函数:虚函数带来了 vptr 和 vtable 的开销。如果某些函数在派生类中不会被重写,就不要声明为虚函数。
- 使用聚合(composition)替代继承:在某些情况下,使用聚合关系而不是继承关系可以更灵活地控制对象的内存布局。例如,如果一个类需要复用另一个类的功能,但不需要继承其接口,可以通过包含另一个类的对象作为成员来实现。
class Helper {
public:
void helperFunc() {}
};
class Container {
private:
Helper helper;
public:
void useHelper() {
helper.helperFunc();
}
};
3. 代码示例
- 成员布局优化示例:
class BadLayout {
public:
char smallChar; // 1 字节
double largeDouble; // 8 字节,由于 char 后需要填充 7 字节以满足 double 对齐
int mediumInt; // 4 字节,又可能需要填充 4 字节
};
class GoodLayout {
public:
double largeDouble; // 8 字节
int mediumInt; // 4 字节,不需要填充
char smallChar; // 1 字节,这里可能填充 3 字节,但总体比 BadLayout 好
};
- 继承优化示例(避免不必要虚函数):
class BaseNoVirtual {
public:
void nonVirtualFunc() {} // 不需要被重写,不声明为虚函数
int data;
};
class DerivedFromNoVirtual : public BaseNoVirtual {
public:
void useBaseFunc() {
nonVirtualFunc();
}
};
class BaseVirtual {
public:
virtual void virtualFunc() {} // 不必要的虚函数
int data;
};
class DerivedFromVirtual : public BaseVirtual {
public:
void useBaseFunc() {
virtualFunc();
}
};
在这个示例中,BaseNoVirtual
没有虚函数,相比 BaseVirtual
,其对象没有 vptr 的开销,在性能和内存占用上更优。