Handle/body idiom
shared_ptr 一个通常用法是用来实现 handle/body 模式. handle/body模式可以避免在头文件中暴露实现.
例子 shared_ptr_example2_test.cpp 包含头文件shared_ptr_example2.hpp .头文件中使用 shared_ptr 保存了了一incomplete type , 从而隐藏了实现.当调用实例的成员函数时,需要完整类型.这部分内容出现在shared_ptr_example2_test.cpp 这个实现文件中.需要注意的是, 实现文件中出现的显式析构函数其实不是必须的. ~shared_ptr 不像 ~scoped_ptr 一样,需要一个 complete type.
线程安全
shared_ptr提供与内置类型一样的线程安全级别.一个shared_ptr实例可以被多个线程同时读.不同的shared_ptr实例(即使这些shared_ptr底层共同拥有同一个引用计数)可以被不同的线程同时写入(通过 reset , 或者 operator= 操作),
任何同时写的操作将会产生未定义行为.
示例:
shared_ptr<int> p(new int(42));
//--- Example 1 ---
// thread A
shared_ptr<int> p2(p); // reads p
// thread B
shared_ptr<int> p3(p); // OK, multiple reads are safe
//--- Example 2 ---
// thread A
p.reset(new int(1912)); // writes p
// thread B
p2.reset(); // OK, writes p2
//--- Example 3 ---
// thread A
p = p3; // reads p3, writes p
// thread B
p3.reset(); // writes p3; undefined, simultaneous read/write
//--- Example 4 ---
// thread A
p3 = p2; // reads p2, writes p3
// thread B
// p2 goes out of scope: undefined, the destructor is considered a "write access"
//--- Example 5 ---
// thread A
p3.reset(new int(1));
// thread B
p3.reset(new int(2)); // undefined, multiple writes
自boost release 版本 1.33.0起, shared_ptr 在以下平台采用 lock-free 实现:
- GNU GCC on x86 or x86-64;
- GNU GCC on IA64;
- Metrowerks CodeWarrior on PowerPC;
- GNU GCC on PowerPC;
- Windows.
[ 仅仅在某些地方定义 BOOST_SP_DISABLE_THREADS ,而不是整个工程范围内, 编译单元将违背 one definition rule (注 1),产生未定义行为.不管怎样,实现满足了一些编译单元使用非原子引用计数的需求.只不过,没有任何保障. ]
你可以通过定义宏 BOOST_SP_USE_PTHREADS 关闭lock-free 实现,使用通用的 pthread_mutex_t-based 实现.
FAQ
Q: 实际应用中有多种智能指针,平衡不同的场景.为什么智能指针库只提供一种实现?如果能够提供多种类型的智能指针,就可以通过实验来确定哪种更适合实际的手头工作了.这样不是很有用么?
A: 智能指针库的一个重要目标就是提供一种标准的共享所有权的指针.仅仅对于一种类型的指针对于库接口的稳定十分重要.因为不同的智能指针之间通常不能够互相操作,比如: 引用计数指针就不能与链表指针共享所有权.
Q: 为什么 shared_ptr 没有提供用于用户定制的模板参数?
A: 参数化并不是那么好用. shared_ptr 模板被精心设计用来满足一般的,没有大量定制的需求.也许未来会有一天,一个高度可配置的smart_ptr可能会非常易用,而且不容易出错.但是在那之前,shared_ptr仍旧是大量应用首选的智能指针.(对参数化智能指针感兴趣的可以阅读一些Modern C++ Design by Andrei Alexandrescu).
Q: 我仍旧不那么认为.默认参数可以隐藏参数化的复杂性.那么,为什么不那么做呢?
A: 参数化影响了实际的类型[注2].所以.
Q: 为什么不使用链表实现?
A: 链表会增加一个指针,但是链表并没有足够的好处来平衡这个问题.除此之外,实现链表的线程安全是比较昂贵的.
Q: 为什么shared_ptr不提供一个向 T*的自动转换?
A: 这种自动转换容易出错.
r
Q: 为什么shared_ptr 提供 use_count();
A: 用于调试.比如跟踪循环依赖.
Q: 为什么shared_ptr不完成一些复杂性需求?
A: 因为复杂的需求会限定shared_ptr的实现,使shared_ptr变得复杂,并且不会对shared_ptr的用户带来明显的好处.比如: 当面对严格的复杂的需求时,错误检查的实现可能会变得不一致.
Q: shared_ptr为什么没有一个 release() 函数?
A: 除非shared_ptr拥有唯一所有权,shared_ptr不能放弃所有权.因为可能还会有其他拷贝释放指针.
shared_ptr<int> a(new int);
shared_ptr<int> b(a); // a.use_count() == b.use_count() == 2
int * p = a.release();
// Who owns p now? b will still call delete on it in its destructor.
此外, release()返回的指针可能不能够被可靠地释放掉.因为源shared_ptr创建时,可能定制了 deleter.
Q: 为什么operator-> 是const, 而他的返回值是一个指向内部元素的非常量指针呢?
A: 以拷贝指针的方式浅拷贝[注3],包括原始指针,通常不会传递其常量性质.这么做也没有什么意义,因为把一个 const 指针赋值给一个 non-const 指针式被允许的, 之后就可以通过这个non-const 指针改动对象了. shard_ptr 就是最接近原始指针的对象.
注 1: one definition rule 用于防止重复定义变量,类,枚举,函数以及模板 定义. 相同编译单元只能有一份定义,不同编译单元可以有多份定义,但是必须保证一致.
注 2: 这里应该是指配置的参数实际上会操作shared_ptr的类型T.
注 3: shallow copy: 当A拷贝B时,A和B指向同一块内存时,这种拷贝就是浅拷贝.