您的问题似乎很简单:调用“ptr->something()”和“instance.something()”有什么区别?
从功能“某物”的角度来看,绝对没有。
#include <iostream>
struct Foo {
void Bar(int i) { std::cout << i << "\n"; }
};
int main() {
Foo concrete;
Foo* dynamic = new Foo;
concrete.Bar(1);
dynamic->Bar(2);
delete dynamic;
}
编译器只发出一个必须处理这两种情况的 Foo::Bar() 实例,因此没有任何区别。
唯一的变化(如果有的话)是在呼叫站点。调用dynamic->Bar()
编译器时,将发出等效于this = dynamic; call Foo0Bar
将“动态”的值直接传输到“this”所在的位置(寄存器/地址)的代码。在 的情况下concrete.Bar
,concrete 将在堆栈上,因此它会发出稍微不同的代码来将堆栈偏移量加载到相同的寄存器/内存位置并进行调用。函数本身将无法分辨。
- - 编辑 - -
这是来自“g++ -Wall -o test.exe -O1 test.cpp && objdump -lsD test.exe | c++filt”的程序集,上面的代码主要是:
main():
400890: 53 push %rbx
400891: 48 83 ec 10 sub $0x10,%rsp
400895: bf 01 00 00 00 mov $0x1,%edi
40089a: e8 f1 fe ff ff callq 400790 <operator new(unsigned long)@plt>
40089f: 48 89 c3 mov %rax,%rbx
4008a2: be 01 00 00 00 mov $0x1,%esi
4008a7: 48 8d 7c 24 0f lea 0xf(%rsp),%rdi
4008ac: e8 47 00 00 00 callq 4008f8 <Foo::Bar(int)>
4008b1: be 02 00 00 00 mov $0x2,%esi
4008b6: 48 89 df mov %rbx,%rdi
4008b9: e8 3a 00 00 00 callq 4008f8 <Foo::Bar(int)>
4008be: 48 89 df mov %rbx,%rdi
4008c1: e8 6a fe ff ff callq 400730 <operator delete(void*)@plt>
4008c6: b8 00 00 00 00 mov $0x0,%eax
4008cb: 48 83 c4 10 add $0x10,%rsp
4008cf: 5b pop %rbx
4008d0: c3 retq
我们的成员函数调用在这里:
混凝土钢筋(1)
4008a2: be 01 00 00 00 mov $0x1,%esi
4008a7: 48 8d 7c 24 0f lea 0xf(%rsp),%rdi
4008ac: e8 47 00 00 00 callq 4008f8 <Foo::Bar(int)>
动态->酒吧(2)
4008b1: be 02 00 00 00 mov $0x2,%esi
4008b6: 48 89 df mov %rbx,%rdi
4008b9: e8 3a 00 00 00 callq 4008f8 <Foo::Bar(int)>
显然“rdi”被用来保存“this”,第一个使用堆栈相对地址(因为concrete
在堆栈上),第二个简单地复制“rbx”的值,它具有来自“new”的返回值早些时候(mov %rax,%rbx
在调用 new 之后)
---- 编辑 2 ----
除了函数调用本身之外,对于必须在对象内构造、拆除和访问值的实际操作而言,堆栈通常更快。
{
Foo concrete;
foo.Bar(1);
}
通常比
Foo* dynamic = new Foo;
dynamic->Bar(1);
delete dynamic;
因为第二种变体必须分配内存,而且通常内存分配器很慢(它们通常有某种锁来管理共享内存池)。此外,为此分配的内存可能是缓存冷的(尽管大多数股票分配器会将块数据写入页面,导致在您开始使用它时它变得有点缓存热,但这可能会导致页面错误,或将其他东西推出缓存)。
使用堆栈的另一个潜在优势是通用缓存一致性。
int i, j, k;
Foo f1, f2, f3;
// ... thousands of operations populating those values
f1.DoCrazyMagic(f1, f2, f3, i, j, k);
如果内部没有外部引用DoCrazyMagic
,那么所有操作都将发生在一个小的内存区域内。相反,如果我们这样做
int *i, *j, *k;
Foo *f1, *f2, *f3;
// ... thousands of operations populating those values
f1->DoCrazyMagic(*f1, *f2, *f3, *i, *j, *k);
可以想象,在复杂的场景中,变量会分布在多个页面上,并可能导致多个页面错误。
然而——如果“数千个操作”足够密集和复杂,我们放置的堆栈区域i, j, k, f1, f2 and f3
可能不再是“热”的。
换句话说:如果您滥用堆栈,它也会成为一种有争议的资源,并且相对于堆使用的优势会被边缘化或消除。