前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >【C++】智能指针:shared_ptr

【C++】智能指针:shared_ptr

作者头像
灰子学技术
发布2020-12-08 17:02:03
1.2K0
发布2020-12-08 17:02:03
举报
文章被收录于专栏:灰子学技术灰子学技术

一、产生的原因

shared_ptr的产生与unique_ptr类似,都是为了解决raw pointer的new和delete的成对使用,导致的野指针、内存泄漏、重复释放内存等。

不过shared_ptr与unique_ptr场景又有所不同,这里主要是一个raw pointer在不同的代码块之间传来传去的场景,或者指针指向的内存比较大,这段内存可以切分成很多小部分,但是他们却需要共享彼此的数据。参考官方文档:

std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer. Several shared_ptr objects may own the same object. https://en.cppreference.com/w/cpp/memory/shared_ptr

二、特性

shared_ptr 有两个特性: 特性1: 对raw pointer进行了一层封装,让C++程序员不用在担心何时去释放分配好的内存。

特性2: 共享,使用shared_ptr的指针可以共享同一块内存中的数据。

思想是:该类型智能指针在实现上采用的是引用计数机制,即便有一个 shared_ptr 指针放弃了堆内存的“使用权”(引用计数减 1),也不会影响其他指向同一堆内存的 shared_ptr 指针(只有引用计数为 0 时,堆内存才会被自动释放)。

参见官方文档:

The object is destroyed and its memory deallocated when either of the following happens: 1.the last remaining shared_ptr owning the object is destroyed; 2.the last remaining shared_ptr owning the object is assigned another pointer via operator= or reset(). https://en.cppreference.com/w/cpp/memory/shared_ptr

三、常用操作的示例

1. 构造函数:

代码语言:javascript
复制
#include <memory>
#include <iostream>
 
struct Foo {
    Foo() { std::cout << "Foo...\n"; }
    ~Foo() { std::cout << "~Foo...\n"; }
};
 
struct D { 
    void operator()(Foo* p) const {
        std::cout << "Call delete from function object...\n";
        delete p;
    }
};
 
int main()
{
    {
        std::cout << "constructor with no managed object\n";
        std::shared_ptr<Foo> sh1;
        bool ok = sh1.get()==nullptr;
        std::cout<<ok<<'\n';
    }
    // copy构造函数的话,引用计数都会增加
    {
        std::cout << "constructor with object\n";
        std::shared_ptr<Foo> sh2(new Foo);
        std::shared_ptr<Foo> sh3(sh2);
        std::cout << sh2.use_count() << '\n'; 
        std::cout << sh3.use_count() << '\n';
    }
   // 可以指定删除的函数,并传递给构造函数
    {
        std::cout << "constructor with object and deleter\n";
        std::shared_ptr<Foo> sh4(new Foo, D());
        std::shared_ptr<Foo> sh5(new Foo, [](auto p) {
           std::cout << "Call delete from lambda...\n";
           delete p;
        });
    }
}
输出:
constructor with no managed object
1 // shared_ptr 默认构造函数分配的是空指针
constructor with object
Foo...
2 // sh2 和sh3指向的都是同一个内存,所以他们的引用计数都是2
2
~Foo...
constructor with object and deleter
Foo...
Foo...
Call delete from lambda...
~Foo...
Call delete from function object...
~Foo..

2.reset :

代码语言:javascript
复制
#include <memory>
#include <iostream>
 
struct Foo {
    Foo(int n = 0) noexcept : bar(n) {
        std::cout << "Foo: constructor, bar = " << bar << '\n';
    }
    ~Foo() {
         std::cout << "Foo: destructor, bar = " << bar << '\n';
    }
    int getBar() const noexcept { return bar; }
private:
    int bar;
};
 
int main()
{
    std::shared_ptr<Foo> sptr = std::make_shared<Foo>(1);
    std::cout << "The first Foo's bar is " << sptr->getBar() << "\n";
    std::cout<<"refer_count:"<<sptr.use_count()<<std::endl;
    // reset the shared_ptr, hand it a fresh instance of Foo
    // (the old instance will be destroyed after this call)
    sptr.reset(new Foo);
    std::cout << "The second Foo's bar is " << sptr->getBar() << "\n";
    std::cout<<"refer_count:"<<sptr.use_count()<<std::endl;
}
输出:
Foo: constructor, bar = 1 // 初始化第一个指针
The first Foo's bar is 1
refer_count:1 // 这里会把引用计数设置为1
Foo: constructor, bar = 0 // 初始化第二个指针
Foo: destructor, bar = 1 // 析构掉第1个指针
The second Foo's bar is 0 // 验证第二个指针的变量是0
refer_count:1 // 第一个释放了,第二个新建了,引用计数还是1
Foo: destructor, bar = 0 // 退出main函数析构掉

3. make_shared

代码语言:javascript
复制
// make_shared example
#include <iostream>
#include <memory>

int main () {
  std::shared_ptr<int> foo = std::make_shared<int> (10);
  // same as: make_shared是推荐的用法,因为它会一次性将raw pointer和引用计数的内存同时分配好
  std::shared_ptr<int> foo2 (new int(10));
  auto bar = std::make_shared<int> (20);
  auto baz = std::make_shared<std::pair<int,int>> (30,40);
  std::cout << "*foo: " << *foo << '\n';
  std::cout << "*bar: " << *bar << '\n';
  std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n';

  return 0;
}
输出:
*foo: 10
*bar: 20
*baz: 30 40

4.allocate_shared

代码语言:javascript
复制
// allocate_shared example
#include <iostream>
#include <memory>
int main () {
  std::allocator<int> alloc;    // the default allocator for int
  std::default_delete<int> del; // the default deleter for int
  std::shared_ptr<int> foo = std::allocate_shared<int> (alloc,10);
  
  auto bar = std::allocate_shared<int> (alloc,20);
  auto baz = std::allocate_shared<std::pair<int,int>> (alloc,30,40);
  
  std::cout << "*foo: " << *foo << '\n';
  std::cout << "*bar: " << *bar << '\n';
  std::cout << "*baz: " << baz->first << ' ' << baz->second << '\n';
  return 0;
}
输出:
*foo: 10
*bar: 20
*baz: 30 40

四、主要函数实现

shared_ptr的代码声明如下所示:

代码语言:javascript
复制
// file: memory

template<class _Tp>>
class shared_ptr {
public:
    typedef _Tp element_type;
private:
    element_type*           __ptr_; // raw pointer的指针
    __shared_weak_count*    __cntrl_; // 引用计数的实现也是指针

    struct __nat{int __for_bool_;}; // placeholder

    // ...
};
// 如果 Yp* 能够转换成 _Tp*,则可以由 _Yp* 构造一个shared_ptr<_Tp>
template<class _Tp>
template<class _Yp>
shared_ptr<_Tp>::shared_ptr(_Yp* __p,
        typename enable_if<is_convertible<_Yp*, element_type*>::value, __nat>::type)
        : __ptr_(__p) {
        unique_ptr<_Yp> __hold(__p);
        typedef __shared_ptr_pointer<_Yp*, default_delete<_Yp>, allocator<_Yp> >_CntrBlk;
        __cntrl_ = new _CntrBlk(__p, default_delete<_Yp>(), allocator<_Yp>());
        __hold.release();
}

// copy constructor, increment reference count
template<class _Tp>
inline shared_ptr<Tp>::shared_ptr(const shared_ptr& __r) noexcept
    : __ptr(__r.__ptr_), __cntrl_(__r.__cntrl_) {
    if (__cntrl_)
        __cntrl_->__add_shared();
}

// move constructor, does't increment reference count
template<class _Tp>
inline shared_ptr<T>::shared_ptr(shared_ptr&& __r) noexcept
    : __ptr_(__r.__ptr_), __cntrl_(__r.__cntrl) {
        __r.__ptr_ = 0;
        __r.__cntrl_ = 0;
}

template<class _Tp>
shared_ptr<_Tp>::~shared_ptr(){
    if (__cntrl_) // 根据引用计数的数量来进行不同的操作
        __cntrl_->__release_shared();
}

涉及到的引用计数类的声明

代码语言:javascript
复制

class __shared_count {  // 引用计数的操作类
    // not copy constructible and not assignable
    __shared_count(const __shared_count&);
    __shared_count& operator=(const __shared_count&);

protected:
    long __shared_owners_; // how many owners do I have?
    virtual ~__shared_count();

public:
    explicit __shared_count(long __refs = 0) noexcept 
: __shared_owners(__refs){}

        void __add_shared() noexcept;
        bool __release_shared() noexcept;
};

class __shared_weak_count : private __shared_count {
    long __shared_weak_owners_;

public:
    explicit __shared_weak_count(long __refs = 0) noexcept {
        : __shared_count(__refs), 
          __shared_weak_owners(__refs) {}

protected:
    virtual ~__shared_weak_count();

public:
    void __add_shared() noexcept;
    void __add_weak() noexcept;
    void __release_shared() noexcept;
    void __release_weak() noexcept;
    long use_count() const noexcept { return __shared_count::use_count();}

private:
    virtual void __on_zero_shared_weak() noexcept = 0;
};

补充资料: http://www.cplusplus.com/reference/memory/shared_ptr/shared_ptr/

https://en.cppreference.com/w/cpp/memory/shared_ptr

http://c.biancheng.net/view/7898.html

https://blog.csdn.net/thinkerleo1997/article/details/78754919

https://blog.csdn.net/shaosunrise/article/details/85228823

https://www.jianshu.com/p/d365bfbb83a3

本文参与 腾讯云自媒体分享计划,分享自微信公众号。
原始发表:2020-11-25,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 灰子学技术 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体分享计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
领券
问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档