多年前写了一篇shared_ptr简介的博客,现在觉得写得不好,就重写一下。加了一些代码和新的内容: aliasing constructor 和 owner_before.
shared_ptr 概述
template<class T> class shared_ptr;
共享指针(shared pointer)管理指针的存储,提供有限的垃圾回收的功能,可能会与其他对象共享此指针管理。
shared_ptr
类型的对象具有获得一个指针所有权的能力,并且能分享此所有权: 当最后一个所有者释放所有权的时候,指针会被delete。
当 shared_ptr 对象被销毁(destroyed) ,或者一旦它们被赋值操作符赋于新的值,或者它们显式调用shared_ptr::reset
, 它们就会释放对共同所占有对象的所有权。 一旦所有的shared_ptr对象(共享一个指针的所有权的对象)都释放了所有权,该被管理的对象就会被删除(一般通过调用::delete
来删除),但在构造函数中可以指定一个不同的deleter。
shared_ptr 对象只能通过拷贝它们的值来共享所有权:如果2个 shared_ptr 都构造自同一个非shared_ptr指针,它们两个都会拥有这个指针却并不会share它。
这会导致潜在的访问问题:当这2个shared_ptr其中之一release了这个指针(即删除了所指对象)时,另一个shared_ptr就会指向一个无效的地址。
(注: 以下code可以说明上面的描述:
示例程序-1:
int *p = new int(10);
shared_ptr<int> s1(p);
shared_ptr<int> s2(p);
cout << "s1: " << *(s1.get()) << endl;
cout << "s2: " << *(s2.get()) << endl;
s1.reset();
cout << "s2: " << *(s2.get()) << endl; // will crash
)
另外,shared_ptr对象可以在共享某个指针的所有权的同时,其实指向的是另一个对象。
这个能力被称为别名(aliasing
),它一般被用来指向成员对象,但拥有(own)这些成员对象所属的对象。
因此,一个shared_ptr其实和以下2个指针相关:
- A stored pointer,就是所指向的指针,即
operator *
来解引用(dereference)的那个指针,也就是用get()
得到的那个指针; - An owned pointer(可能被共享的),也就是这些所有者(ownership group)负责去 delete 的指针,也就是对其进行使用计数的那个指针。
(注:
这里"An owned pointer"指的是真正共享的指针,即上述示例代码中的p
而"A stored pointer" 指的是 shared_ptr 对象中成员变量的那个指针,当初构造时,将p
赋给了这个成员变量。)
一般来说,stored pointer和owned pointer指向相同的东西,但是alias shared_ptr对象(用alias constructor构建的对象和它们的copy)的这2个指针却可能指向不同的对象。
一个不拥有任何指针的shared_ptr被称作 empty shared_ptr. (注: 调用reset()
之后,就是empty)
一个什么对象都不指向的shared_ptr被称作 null shared_ptr,并且不能被解引用(dereferenced).
一个empty shared_ptr不一定是一个null shared_ptr,反之亦然。
示例程序-2:
#include <memory>
#include <iostream>
using namespace std;
int main()
{
shared_ptr<int> s1;
if (s1.get() == NULL) {
cout << "s1 is NULL\n"; // printed
}
else {
cout << "Something in get()!\n";
}
int *p = new int(10);
shared_ptr<int> s2(p);
s2.reset();
if (s2.get() == NULL) {
cout << "s2 is NULL\n"; // printed
}
else {
cout << "Something in s1.get()!\n";
}
// cout << *s2 << endl; // must crash, cannot dereference NULL
cout << *p << endl; // Wild pointer, may crash or print invalid value
return 0;
}
)
通过operator *
和operator ->
以访问所指向的对象,shared_ptr复制了有限的指针功能。因为安全的原因,shared_ptr对象不提供指针运算。
一个相关的类,weak_ptr
,能够和shared_ptr对象共享指针却并不拥有它们。
(注: weak_ptr一般用来解决循环引用问题)
aliasing 构造函数
aliasing 是 shared_ptr 的 10 种构造函数的一种。函数声明为:
template <class U> shared_ptr (const shared_ptr<U>& x, element_type* p) noexcept;
(注:
这里的 U 指的是 x 的类型,而不是这个shared_ptr对象定义时所用的类型;
这个shared_ptr对象定义时所用的类型应该和 element_type 是一样的。参见下面的示例程序.
gcc 7.4 的 STL 源码中的定义如下,确实和 cplusplus.com 上所述一致。
template<typename _Yp>
shared_ptr(const shared_ptr<_Yp>& __r, element_type* __p) noexcept
: __shared_ptr<_Tp>(__r, __p) { }
)
它是一种拷贝构造函数,但是它内部的 stored pointer 是 p (注: 而不是 x 的 stored pointer)
构造出来的 shared_ptr 对象并不会拥有 p (注: 即并不是像 shared_ptr<int> ps(p);
这样), 也不会管理 p 的存储空间;
这个 shared_ptr 对象还是和 x 一起共同拥有 x 所管理的对象(注: 即x的 owned pointer),它会delete x的 owner pointer,而不是 p.
(注: 第二个参数p在这里只是用来被赋值给新构造出来的shared_ptr对象内部的 stored pointer.)
这个aliasing构造函数可以被用来指向已经被管理的对象的成员。
示例程序-3:
#include <memory>
#include <iostream>
using namespace std;
struct C {int* data;};
int main()
{
shared_ptr<C> p1(new C);
shared_ptr<int> p2(p1, p1->data); // Note, type is int, not C
shared_ptr<C> p3(p1);
cout << "p2: " << p2.use_count() << '\n'; // 3
cout << "p3: " << p3.use_count() << '\n'; // 3
cout << hex << p1->data << endl; // 0x6000003dc
cout << hex << p2.get() << endl; // 0x6000003dc
cout << hex << p1.get() << endl; // 0x600000440
cout << hex << p3.get() << endl; // 0x600000440
return 0;
}
由以上程序可见,
- aliasing构造函数构造出的shared_ptr对象的stored pointer就是构造函数的第2个参数;
- 而这第2个参数其实是所管理的对象内部的一个指针;
示例程序-4:
#include <iostream>
#include <memory>
using namespace std;
struct C { int* data; };
int main() {
std::shared_ptr<C> obj(new C);
obj->data = new int{150};
cout << *obj->data << std::endl; // 150
std::shared_ptr<int> p(obj, obj->data);
// 150, obj->data是 p 的 stored pointer, 所以对它做derefrence
cout << "*p: " << *p << '\n';
std::cout << "p.get() = " << p.get() << std::endl; // 0x600000480
delete p.get(); // Only do this for aliasing
cout << "p.use_count: " << p.use_count() << '\n'; // 2
cout << "obj.use_count: " << obj.use_count() << endl; // 2
cout << "-------------------------\n";
int * m = new int{99};
cout << m << std::endl;
std::cout << "p.get() = " << p.get() << std::endl; // 0x600000480
p.reset(m);
cout << "*p: " << *p << '\n'; // 99
cout << "p.use_count: " << p.use_count() << '\n'; // 1
cout << "obj.use_count: " << obj.use_count() << endl; // 1
// delete p.get(); // will crash, as this is after reset(m)
p.reset(); // empty object
cout << "p.use_count: " << p.use_count() << '\n'; // 0
cout << "obj.use_count: " << obj.use_count() << endl; // 1
return 0;
}
owner_before 成员函数
owner_before 是 shared_ptr 的 成员函数,它的原型如下:
template <class U> bool owner_before (const shared_ptr<U>& x) const;
template <class U> bool owner_before (const weak_ptr<U>& x) const;
owner_before 会被owner_less
的实现所调用。
owner_before 比较的是 owned pointer.
而 operator <
比较的是 stored pointer.
示例程序-5
#include <iostream>
#include <memory>
using namespace std;
int main () {
int * p = new int (10);
shared_ptr<int> a (new int (20));
shared_ptr<int> b (a,p); // alias constructor
cout << " a.get() = " << a.get() << endl; // 0x600000460
cout << " b.get() = " << b.get() << endl; // 0x600000440
cout << std::boolalpha;
cout << " a < b " << (a<b) << "\n"; // a < b false
cout << " a > b " << (a>b) << "\n"; // a > b true
cout << std::boolalpha;
cout << " a.owner_before(b) = " << a.owner_before(b) << "\n"; // false
cout << " b.owner_before(a) = " << b.owner_before(a) << "\n"; // false
delete p;
struct C {
int * a;
int * b;
};
C * c1 = new C;
c1->a = new int(10);
c1->b = new int(20);
shared_ptr<C> c2(c1);
shared_ptr<int> c3(c2, c1->a);
shared_ptr<int> c4(c2, c1->b);
cout << std::boolalpha;
cout << " c2.owner_before(c3) = " << c2.owner_before(c3) << "\n"; // false
cout << " c3.owner_before(c2) = " << c3.owner_before(c2) << "\n"; // false
cout << " c2.owner_before(c4) = " << c2.owner_before(c4) << "\n"; // false
cout << " c4.owner_before(c2) = " << c4.owner_before(c2) << "\n"; // false
cout << " c3.owner_before(c4) = " << c3.owner_before(c4) << "\n"; // false
cout << " c4.owner_before(c3) = " << c4.owner_before(c3) << "\n"; // false
C * c5 = new C;
shared_ptr<C> c6(c5);
cout << " c2.owner_before(c6) = " << c2.owner_before(c6) << "\n"; // true
cout << " c6.owner_before(c2) = " << c6.owner_before(c2) << "\n"; // false
delete c3.get();
delete c4.get();
return 0;
}
上面这段程序充分说明了 owner_before
比较的是 owned pointer. 即使是 aliasing constuctor 出来的各种形式,也依然会相等。
另外,对于比较复杂的继承的情况,请看陈硕的例子(原文在这里)
示例程序-6
#include <cstdio>
#include <memory>
class BaseA { int a; };
class BaseB { double b; };
class Derived: public BaseA, public BaseB {};
int main()
{
std::shared_ptr<Derived> pd(new Derived);
std::shared_ptr<BaseB> pb(pd);
printf("%p %p\n", pd.get(), pb.get()); // 0x6000003a0 0x6000003a8
printf("%d %d\n", pd < pb, pb < pd); // 0 0 but stored pointers seem different
printf("%d %d\n", pd.owner_before(pb), pb.owner_before(pd)); // 0 0
std::shared_ptr<void> p0(pd), p1(pb);
printf("%p %p\n", p0.get(), p1.get()); // 0x6000003a0 0x6000003a8
printf("%d %d\n", p0 < p1, p1 < p0); // 1 0
printf("%d %d\n", p0.owner_before(p1), p1.owner_before(p0)); // 0 0
}
这里 pb.get() 和 pd.get() 不同,是多重继承时内存布局的影响。
但为什么 pd < pb 和 pb < pd 都是 false, 则是 C++ 中的另一个坑了。且听下回分解。
参考文献
- http://www.cplusplus.com/reference/memory/shared_ptr
- http://www.cplusplus.com/reference/memory/shared_ptr/owner_before/
- https://www.zhihu.com/question/24816143
(完)