假设有这么个类:
class A
{
private:
int b;
public:
A(int c):b(c) { cout << "call constructor..." << endl;}
~A() { cout << "call destructor..." << endl;}
int getValue() { return b;}
};
当创建指向 A 对象的智能指针 shared_ptr 时,很多地方都提到 new 至少执行两次分配,一次用于对象 A 的创建,一次用于智能指针 shared_ptr 的控制块,而如果用 make_shared() 方法通常只需执行一次分配,效率上有所提升。
这点 Herb Sutter 在他的博客 🔗GotW #89 Solution: Smart Pointers 也有提到:
First, with make_shared the code is simpler. Write for clarity and correctness first.
Second, using make_shared is more efficient. The shared_ptr implementation has to maintain housekeeping information in a control block shared by all shared_ptrs and weak_ptrs referring to a given object...
If you allocate the object separately via a raw new expression, then pass it to a shared_ptr, the shared_ptr implementation has no alternative but to allocate the control block separately...
We’d like to avoid doing two separate allocations here. If you use make_shared to allocate the object and the shared_ptr all in one go, then the implementation can fold them together in a single allocation...
下面我们从汇编层面看看二者区别,主方法中代码如下:
shared_ptr<A> sn(new A(5));
shared_ptr<A> sm = make_shared<A>(6);
第一行对应汇编语句如下:
0x0000000100000e64 <+20>: call 0x100003914
0x0000000100000e69 <+25>: mov %rax,%rcx
0x0000000100000e6c <+28>: mov %rax,%rdx
0x0000000100000e6f <+31>: mov $0x5,%esi
0x0000000100000e74 <+36>: mov %rax,%rdi
0x0000000100000e77 <+39>: mov %rcx,-0x48(%rbp)
0x0000000100000e7b <+43>: mov %rdx,-0x50(%rbp)
0x0000000100000e7f <+47>: call 0x100000f20 <_ZN1AC1Ei>
0x0000000100000e84 <+52>: jmp 0x100000e89 <main()+57>
0x0000000100000e89 <+57>: movl $0x0,-0x28(%rbp)
0x0000000100000e90 <+64>: mov -0x28(%rbp),%edx
0x0000000100000e93 <+67>: lea -0x18(%rbp),%rdi
0x0000000100000e97 <+71>: mov -0x50(%rbp),%rsi
0x0000000100000e9b <+75>: call 0x100000f50 <_ZNSt3__110shared_ptrI1AEC1IS1_EEPT_NS_9enable_ifIXsr17__compatible_withIS4_S1_EE5valueENS2_5__natEE4typeE>
0x0000000100000ee3 <+147>: call 0x10000390e
0x0000000100000ee8 <+152>: jmp 0x100000f07 <main()+183>
0x0000000100000f07 <+183>: mov -0x20(%rbp),%rdi
0x0000000100000f0b <+187>: call 0x100003878
0x0000000100000f10 <+192>: ud2
先是调用了地址在 0x100003914 处的方法,试着通过 info 去看并没查到对应方法名:
(gdb) info symbol 0x100003914
No symbol matches 0x100003914.
查看代码位置:
lucas@lucasdeMacBook-Pro testCpp % objdump -h test
test: file format mach-o 64-bit x86-64
Sections:
Idx Name Size VMA Type
0 __text 00002a27 0000000100000e50 TEXT
1 __stubs 000000cc 0000000100003878 TEXT
2 __stub_helper 000000f6 0000000100003944 TEXT
3 __gcc_except_tab 00000274 0000000100003a3c DATA
4 __cstring 0000006b 0000000100003cb0 DATA
5 __const 00000102 0000000100003d1b DATA
6 __unwind_info 000001d8 0000000100003e20 DATA
7 __got 00000068 0000000100004000 DATA
8 __const 000000c8 0000000100004068 DATA
9 __la_symbol_ptr 00000110 0000000100008000 DATA
10 __data 00000008 0000000100008110 DATA
方法地址位于 __stubs Section,__stubs 可理解为动态库链接的桩,具体方法名可在 MachOView 中查看:
根据地址 0x100003914 可看到对应的 value 是 _Znwm,即 operator new() 方法,在执行几次 mov 指令后还要依次调用如下几个方法。
(gdb) info symbol 0x100000f20
A::A(int) in section .text of /Users/lucas/Documents/study/code/testCpp/test
(gdb) info symbol 0x100000f50
std::__1::shared_ptr<A>::shared_ptr<A>(A*, std::__1::enable_if<__compatible_with<A, A>::value, std::__1::shared_ptr<A>::__nat>::type) in section .text of /Users/lucas/Documents/study/code/testCpp/test
最后还要调用值 _ZdlPv 对应的 operator delete() 方法。可以看到通过 new 创建 shared_ptr 对象一共要执行近 20 条汇编指令,而通过 make_shared() 方法呢?
0x0000000100000ea0 <+80>: movl $0x6,-0x3c(%rbp)
0x0000000100000ea7 <+87>: lea -0x38(%rbp),%rdi
0x0000000100000eab <+91>: lea -0x3c(%rbp),%rsi
0x0000000100000eaf <+95>: call 0x100000f80 <_ZNSt3__1L11make_sharedI1AJiEvEENS_10shared_ptrIT_EEDpOT0_>
0x0000000100000eb4 <+100>: jmp 0x100000eb9 <main()+105>
0x0000000100000eb9 <+105>: lea -0x38(%rbp),%rdi
无需再调用 operator new() 和 operator delete() 方法,且只需执行一次分配即可,总共也就 6 条指令。