简短回答
访问子对象的运行时效率可能没有区别,但使用指针可能会因为多种原因而更慢(下文详细解释)。
此外,还有几个其他事项需要记住:
- 使用指针时,您通常需要单独为子对象分配/释放内存,这需要一些时间(如果您经常这样做,则需要相当长的时间)。
- 使用指针时,可以在不复制的情况下廉价地移动子对象。
谈到编译时间,指针比纯成员更好。对于纯成员,您无法消除Manager
声明对SomeClass
声明的依赖关系。使用指针,您可以通过前向声明来完成。较少的依赖可能导致构建时间较短。
详细信息
我想提供有关子对象访问性能的更多详细信息。我认为,由于以下几个原因,使用指针可能比使用纯成员更慢:
- 使用纯成员时,数据局部性(和缓存性能)很可能更好。通常,您同时访问
Manager
和SomeClass
的数据,并且保证纯成员靠近其他数据,而堆分配可能会将对象和子对象距离很远。
- 使用指针意味着多一层间接寻址。要获取纯成员的地址,可以简单地添加编译时常量偏移量到对象地址(通常与其他汇编指令合并)。当使用指针时,您还需要从成员指针中额外读取一个字以获取实际的子对象指针。有关详细信息,请参见Q1和Q2。
- 别名问题也许是最重要的问题。如果您使用纯成员,则编译器可以假定:您的子对象完全位于您的对象内存中,且不与您的对象其他成员重叠。使用指针时,编译器通常无法假定任何类似的内容:您的子对象可能与您的对象及其成员重叠。因此,编译器必须生成更多的无用的加载/存储操作,因为它认为某些值可能会更改。
以下是最后一个问题的示例(完整代码在这里):
struct IntValue {
int x;
IntValue(int x) : x(x) {}
};
class MyClass_Ptr {
unique_ptr<IntValue> a, b, c;
public:
void Compute() {
a->x += b->x + c->x;
b->x += a->x + c->x;
c->x += a->x + b->x;
}
};
显然,将子对象 a
、b
、c
存储为指针是愚蠢的。我已经测量了对单个对象进行一十亿次调用 Compute
方法所花费的时间。以下是不同配置下的结果:
2.3 sec: plain member (MinGW 5.1.0)
2.0 sec: plain member (MSVC 2013)
4.3 sec: unique_ptr (MinGW 5.1.0)
9.3 sec: unique_ptr (MSVC 2013)
当查看每种情况下最内层循环的生成汇编代码时,很容易理解为什么时间会有如此大的差异:
lea edx, [rcx+rax]
add r8d, edx
lea edx, [r8+rax]
add ecx, edx
lea edx, [r8+rcx]
add eax, edx
sub r9d, 1
jne .L3
add ecx, r8d
add edx, ecx
add ecx, edx
add r8d, edx
add r8d, ecx
dec r9
jne SHORT $LL6@main
add eax, DWORD PTR [rcx]
add eax, DWORD PTR [rdx]
mov DWORD PTR [rdx], eax
add eax, DWORD PTR [r8]
add eax, DWORD PTR [rcx]
mov DWORD PTR [rcx], eax
add eax, DWORD PTR [rdx]
add eax, DWORD PTR [r8]
sub r9d, 1
mov DWORD PTR [r8], eax
jne .L4
mov r9, QWORD PTR [rbx]
mov rcx, QWORD PTR [rbx+8]
mov rdx, QWORD PTR [rbx+16]
mov r8d, DWORD PTR [rcx]
add r8d, DWORD PTR [rdx]
add DWORD PTR [r9], r8d
mov r8, QWORD PTR [rbx+8]
mov rcx, QWORD PTR [rbx]
mov rax, QWORD PTR [rbx+16]
mov edx, DWORD PTR [rcx]
add edx, DWORD PTR [rax]
add DWORD PTR [r8], edx
mov r9, QWORD PTR [rbx+16]
mov rax, QWORD PTR [rbx]
mov rdx, QWORD PTR [rbx+8]
mov r8d, DWORD PTR [rax]
add r8d, DWORD PTR [rdx]
add DWORD PTR [r9], r8d
dec rsi
jne SHORT $LL3@main