简介shared_ptr

多年前写了一篇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

(完)

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值