shared_ptr
1.以何种方式传递
通过下列方式将 shared_ptr 传递给其他函数:
按值传递 shared_ptr。 这将调用复制构造函数,增加引用计数,并使被调用方成为所有者。 此操作的开销很小,但此操作的开销可能很大,具体取决于要传递多少 shared_ptr 对象。 当调用方和被调用方之间的(隐式或显式)代码协定要求被调用方是所有者时,请使用此选项。
按引用或常量引用传递 shared_ptr。 在这种情况下,引用计数不会增加,并且只要调用方不超出范围,被调用方就可以访问指针。 或者,被调用方可以决定基于引用创建一个 shared_ptr,并成为一个共享所有者。 当调用方并不知道被调用方,或当您由于性能原因必须传递一个 shared_ptr 且希望避免复制操作时,请使用此选项。
传递基础指针或对基础对象的引用。 这使被调用方能够使用对象,但不会使其能够共享所有权或延长生存期。 如果被调用方通过原始指针创建一个 shared_ptr,则新的 shared_ptr 独立于原始的,并且不会控制基础资源。 当调用方和被调用方之间的协定明确指定调用方保留 shared_ptr 生存期的所有权时,请使用此选项。
在确定如何传递 shared_ptr 时,确定被调用方是否必须共享基础资源的所有权。 “所有者”是只要它需要就可以使基础资源一直有效的对象或函数。 如果调用方必须保证被调用方可以将指针的生命延长到其(函数)生存期以外,则请使用第一个选项。 如果您不关心被调用方是否延长生存期,则按引用传递并让被调用方复制或不复制它。
如果必须为帮助器函数提供对基础指针的访问权限,并且你知道帮助器函数将使用该指针并且将在调用函数返回前返回,则该函数不必共享基础指针的所有权。 它只需在调用方的 shared_ptr 的生存期内访问指针即可。 在这种情况下,按引用传递 shared_ptr 或传递原始指针或对基础对象的引用是安全的。 通过此方式传递将提供一个小的性能好处,并且还有助于您表达编程意图。
有时,例如,在一个 std::vector<shared_ptr> 中,您可能必须将每个 shared_ptr 传递给 lambda 表达式主体或命名函数对象。 如果 lambda 或函数没有存储指针,则将按引用传递 shared_ptr 以避免调用每个元素的复制构造函数
2. 裸指针
shared_ptr<int> sp = new int(100);//不能用裸指针初始化sp
share_ptr<int> sp3(new int(100));//合法
int *x(new int(100));
shared_ptr<int> sp1(x);
shared_ptr<int> sp2(x);
//x为裸指针,不合法,且sp1 sp2虽然都指向x所指向的内存,但是二者是独立的,当sp1释放时,x会变成空悬指针,sp2也很危险了
shared_ptr<int> sp1(new int(10));
shared_ptr<int> sp2(sp1.get());//get返回的是裸指针,error
sp.reset(sp1.get());
3. 线程安全
(shared_ptr)的引用计数本身是安全且无锁的,但对象的读写则不是,因为 shared_ptr 有两个数据成员,读写操作不能原子化。根据文档(http://www.boost.org/doc/libs/release/libs/smart_ptr/shared_ptr.htm#ThreadSafety), shared_ptr 的线程安全级别和内建类型、标准库容器、std::string 一样,即:
• 一个 shared_ptr 对象实体可被多个线程同时读取(文档例1);
• 两个 shared_ptr 对象实体可以被两个线程同时写入(例2),“析构”算写操作;
• 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁(例3~5)。
请注意,以上是 shared_ptr 对象本身的线程安全级别,不是它管理的对象的线程安全级别。
后文(p.18)则介绍如何高效地加锁解锁。本文则具体分析一下为什么“因为 shared_ptr 有两个数据成员,读写操作不能原子化”使得多线程读写同一个 shared_ptr 对象需要加锁。这个在我看来显而易见的结论似乎也有人抱有疑问,那将导致灾难性的后果,值得我写这篇文章。本文以 boost::shared_ptr 为例,与 std::shared_ptr 可能略有区别。
shared_ptr 的数据结构
shared_ptr 是引用计数型(reference counting)智能指针,几乎所有的实现都采用在堆(heap)上放个计数值(count)的办法(除此之外理论上还有用循环链表的办法,不过没有实例)。具体来说,shared_ptr 包含两个成员,一个是指向 Foo 的指针 ptr,另一个是 ref_count 指针(其类型不一定是原始指针,有可能是 class 类型,但不影响这里的讨论),指向堆上的 ref_count 对象。ref_count 对象有多个成员,具体的数据结构如图 1 所示,其中 deleter 和 allocator 是可选的。
unordered容器时,退化成链表
shared_ptr 作为 unordered_map 的 key
如果把 boost::shared_ptr 放到 unordered_set 中,或者用于 unordered_map 的 key,那么要小心 hash table 退化为链表。http://stackoverflow.com/questions/6404765/c-shared-ptr-as-unordered-sets-key/12122314#12122314
直到 Boost 1.47.0 发布之前,unordered_set<std::shared_ptr > 虽然可以编译通过,但是其 hash_value 是 shared_ptr 隐式转换为 bool 的结果。也就是说,如果不自定义hash函数,那么 unordered_{set/map} 会退化为链表。https://svn.boost.org/trac/boost/ticket/5216
Boost 1.51 在 boost/functional/hash/extensions.hpp 中增加了有关重载,现在只要包含这个头文件就能安全高效地使用 unordered_setstd::shared_ptr 了。
refhttps://blog.csdn.net/solstice/article/details/8547547
4 管理socket FILE
将打开的文件句柄交给shared_ptr管理,如果发生任何不当行为,shared_ptr会帮助我们自动释放资源,程序员也不用自己显式写出fclose。
如果文件不存在,那么shared_ptr管理的是空指针,这样会在fclose发生异常,为了避免,先打开后判断是否为空哈
shared_ptr<FILE> fp(fopen("/test.txt","r"),fclose);
一个自定义类管理套接字资源,自定义一个合适的资源释放函数,更加高效!!
Socket* openSocket()
{
//do something
}
void closeSocket()
{
//do something
}
auto s= openSocket();
if (s != nullptr)
{
shared_ptr<Socket> p(s, closeSocket);
}
else
{
return -1;
}
5. 性能
1. share_ptr的size是裸指针的两倍:指向到该资源的裸指针,指向到该资源的引用计数的裸指针
2. 引用计数的内存必须动态分配
3. 引用计数的递增和递减必须是原子操作
6. 数组与容器
能
1. share_ptr的size是裸指针的两倍:指向到该资源的裸指针,指向到该资源的引用计数的裸指针
2. 引用计数的内存必须动态分配
3. 引用计数的递增和递减必须是原子操作
6. 数组与容器
没有所谓的share_ptr<T[]>,以array vector string替代,但是unique_ptr有