C++函数性能与特性深度解析
立即解锁
发布时间: 2025-08-21 00:09:34 阅读量: 2 订阅数: 4 


C++对象模型内幕:从理论到实践
### C++ 函数性能与特性深度解析
#### 1. 函数效率测试
在一系列测试中,对两个三维点的叉积进行了计算,分别采用了非成员友元函数、成员函数、内联成员函数和虚成员函数的形式。虚成员函数实例还在单继承、多继承和虚继承的情况下执行。
以下是非成员形式的叉积实现代码:
```cpp
void
cross_product( const pt3d &pA, const pt3d &pB )
{
pt3d pC;
pC.x = pA.y * pB.z - pA.z * pB.y;
pC.y = pA.z * pB.x - pA.x * pB.z;
pC.z = pA.x * pB.y - pA.y * pB.x;
}
```
主函数如下(此例中以非成员函数形式调用叉积):
```cpp
main() {
pt3d pA( 1.725, 0.875, 0.478 );
pt3d pB( 0.315, 0.317, 0.838 );
for ( int iters = 0; iters < 10000000; iters++ )
cross_product( pA, pB );
return 0;
}
```
不同测试程序中,`cross_product()` 的实际调用会因表示形式的改变而有所不同。测试结果如下表所示:
| 函数类型 | 优化后 | 未优化 |
| --- | --- | --- |
| 内联成员函数 | 0.08 | 4.70 |
| 非成员友元函数 | 4.43 | 6.13 |
| 静态成员函数 | 4.43 | 6.13 |
| 非静态成员函数 | 4.43 | 6.13 |
| 虚成员函数(CC) | 4.76 | 6.90 |
| 虚成员函数(NCC) | 4.63 | 7.72 |
| 虚成员函数(多继承,CC) | 4.90 | 7.06 |
| 虚成员函数(多继承,NCC) | 4.96 | 8.00 |
| 虚成员函数(虚继承,CC) | 5.20 | 7.07 |
| 虚成员函数(虚继承,NCC) | 5.44 | 8.08 |
从表中数据可以看出,非成员、静态成员和非静态成员函数在内部转换为等效表示,因此这三种形式的性能没有差异。未优化的内联版本性能提升约 25%,而优化后的内联版本结果惊人,这是因为不变的表达式被提出循环,仅计算一次。这表明内联扩展不仅节省了函数调用的开销,还为程序优化提供了额外的机会。
虚函数实例通过引用而非对象调用,以确保调用通过虚机制进行。性能下降幅度在 4% 到 11% 之间,部分成本体现在 `Point3d` 构造函数设置内部 `vptr` 1000 万次的额外开销上。此外,CC 和 NCC(至少在 NCC 的 cfront 兼容模式下)使用 delta - 偏移模型来支持虚函数,这也增加了成本。
在单继承测试中发现,每增加一级继承,虚函数可执行文件的性能时间显著增加。经过分析,这是因为 `cross_product()` 中存在局部 `Point3d` 类对象 `pC`,默认的 `Point3d` 构造函数被调用了 1000 万次,随着单继承层次的增加,构造函数代码的复杂度增加,导致成本上升。多继承调用的额外开销也可以用此来解释。
为了验证构造函数调用的额外开销是否导致性能时间增加,对函数进行了两种改写:
1. 将保存结果的对象作为额外参数添加到函数中:
```cpp
void
cross_product( pt3d &pC, const pt3d &pA, const pt3d &pB )
{
pC.x = pA.y * pB.z - pA.z * pB.y;
// 其余部分相同 ...
}
```
2. 直接在 `this` 对象中计算结果:
```cpp
void
pt3d::
cross_product(const pt3d &pB )
{
x = y * pB.z - z * pB.y;
// 其余部分相同,只是使用此对象的 x、y 和 z ...
}
```
这两种改写在单继承的各个层次上,未优化的执行时间平均统一为 6.90。有趣的是,语言没有提供一种机制来表明默认构造函数的调用是不必要的,只能通过消除局部对象的使用来消除构造函数的调用。
#### 2. 成员函数指针
获取非静态数据成员的地址返回的是该成员在类布局中的字节位置值(加 1),这是一个不完整的值,需要绑定到类对象的地址才能访问成员的实际实例。获取非静态成员函数的地址,如果该函数是非虚函数,返回的是函数文本在内存中的实际地址,同样需要绑定到类对象的地址才能调用成员函数,对象的地址作为所有非静态成员函数所需的 `this` 指针参数。
成员函数指针的声明语法如下:
```cpp
double // 返回类型
( Point::* // 函数所属的类
pmf ) // 成员指针的名称
(); // 参数列表
```
例如:
```cpp
double (Point::*coord)() = &Point::x;
```
用于定义和初始化一个类成员指针,而
```cpp
coord = &Point::y;
```
用于为其赋值。调用时使用成员指针选择运算符,如
```cpp
( origin.*coord )();
```
或
```cpp
( ptr->*coord )();
```
编译器会将它们分别转换为:
```cpp
// 伪 C++ 代码
( coord )( & origin );
```
和
```cpp
// 伪 C++ 代码
( coord )( ptr );
```
成员函数指针声明语法和成员指针选择运算符实际上是 `this` 指针的占位符。静态成员函数没有 `this` 指针,因此其类型为“函数指针”,而非“成员函数指针”。
如果没有虚函数和多继承(包括虚基类),成员指针的使用成本不会比非成员函数指针高。对于没有虚函数或虚基类、多基类的类,编译器可以提供等效的性能。
##### 2.1 支持虚成员函数指针
考虑以下代码片段:
```cpp
float (Point::*pmf)() = &Point::z;
Point *ptr = new Point3d;
```
`pmf` 是一个成员指针,初始化为指向虚函数 `Point::z()`,`ptr` 初始化为指向 `Point3d` 类型的对象。如果直接通过 `ptr` 调用 `z()`:
```cpp
ptr->z();
```
调用的实例是 `Point3d::z()`。如果通过 `pmf` 间接调用 `z()`:
```cpp
( ptr->*pmf)();
```
是否仍然调用 `Point3d::z()` 呢?答案是肯定的。对于虚函数,在编译时无法知道其内存地址,已知的是该函数在虚表中的关联索引。例如,对于以下简化的 `Point` 类声明:
```cpp
class Point
{
public:
virtual ~Point();
float x();
float y();
virtual float z();
// ...
};
```
获取析构函数的地址 `&Point::~Point;` 返回 1,获取 `x()` 或 `y()` 的地址返回它们的实际内存位置,因为它们不是虚函数,而获取 `z()` 的地址 `&Point::z;` 返回 2。通过 `pmf` 调用 `z()` 会在内部转换为以下一般形式的编译时表达式:
```cpp
( * ptr->vptr[ (int)pmf ])( ptr );
```
成员函数指针的求值由于其可以持有双重值以及
0
0
复制全文
相关推荐









