c++智能指针使用总结

shared_ptr real life use case

https://stackoverflow.com/questions/48834271/shared-ptr-real-life-use-cases

一、shared_ptr的数据结构和线程安全性

shared_ptr 是引用计数型智能指针,几乎所有的实现都采用在堆(heap)上放个计数值的办法。具体来说,shared_ptr 包含两个成员,一个是指向 Foo 的指针 ptr,另一个是 ref_count 指针,指向堆上的 ref_count 对象。ref_count 对象有多个成员,具体的数据结构如图 1 所示,其中 deleter 和 allocator 是可选的。
在这里插入图片描述
正是因为ptr和ref_count两个成员变量是分别赋值的,在多线程情况下,可能线程1中ptr成员变量赋值后,切换到了线程2中对ptr和ref_count都做了变更,再回到线程1去给ref_count赋值,从而造成错乱。所以多线程读写同一个 shared_ptr 必须加锁。

参考资料:陈硕的blog 为什么多线程读写 shared_ptr 要加锁 https://www.cppblog.com/Solstice/archive/2013/01/28/197597.html

二、shared_ptr的初始化和赋值

2.1 使用构造函数初始化

std::shared_ptr<int> p1(new int(1));
//std::shared_ptr<int> p1 = new int(1);  //注意这样是错误的,接受指针参数的智能指针构造函数是 explicit 的,
                 //我们不能将一个内置指针隐式转换为一个智能指针,必须使用直接初始化形式来初始化一个智能指针;

2.2 使用make_shared初始化

std::shared_ptr<int> p1 = make_shared<int>(1);

此函数在动态内存(堆)中分配一个对象并初始化它,返回指向此对象的 shared_ptr。当用 make_shared 时,必须指定想要创建的对象的类型。定义方式与模板类相同,在函数名之后跟一个尖括号,在其中给出类型。make_shared 用其参数来构造给定类型的对象。例如,调用 make_shared 时传递的参数必须与 string 的某个构造函数相匹配,调用 make_shared 时传递的参数必须能用来初始化一个 int,依此类推。
make_shared()可以节省一次内存分配, 原来的shared_ptr x(new Foo); 需要为 Foo 和 ref_count 各分配一次内存,现在用 make_shared() 的话,可以一次分配一块足够大的内存,供 Foo 和 ref_count 对象容身。数据结构如下:
在这里插入图片描述

2.3 使用reset初始化

std::shared_ptr<int> pointer;
pointer.reset(new int(1));

2.4 shared_ptr的拷贝构造和拷贝赋值

auto p = make_shared<int>(42); // p指向的对象只有p一个引用者
auto q(p); // 拷贝构造,p和q指向相同对象,此对象有两个引用者

auto r = make_shared<int>(42); // r指向的int只有一个引用者
r = q; //赋值,给r赋值,令它指向另一个地址
       //递增q指向的对象的引用计数, 递减r原来指向的对象的引用计数, r原来指向的对象已没有引用者,会自动释放

2.5 shared_ptr的移动构造和移动赋值

    std::shared_ptr<int> sp1(new int(180)); // 强引用计数从0变1  
    std::shared_ptr<int> sp2(std::move(sp1)); // 移动构造, 一个新的智能指针对象sp2,sp1变成空,sp2指向该内存,强引用计数仍为1
    std::shared_ptr<int> sp3 = std::move(sp2); // 移动赋值,sp2变成空,sp3指向该内存,强引用计数仍为1

三、shared_ptr与容器

四、shared_ptr作为类成员

五、shared_ptr作为参数或者函数返回值

Passing shared pointers by reference rather than value is a stylistic point with arguments for and against. Clearly when passing by reference you are not passing ownership (e.g. increasing the reference count on the object). On the other hand, you are also not taking the overhead either. There is a danger that you under reference objects this way. On a more practical level, with a C++11 compiler and standard library, passing by value should result in a move rather than copy construction and be very nearly free anyway. However, passing by reference makes debugging considerably easier as you won’t be repeatedly stepping into shared_ptr’s constructor.
参考:https://stackoverflow.com/questions/24355804/how-to-properly-use-shared-ptr-in-good-c-apis

六、使用shared_ptr的坑

6.1 在类成员函数中,使用this指针创建shared_ptr

实例代码

class A
{
    std::shared_ptr<A> getA()
    {
        std::shared_ptr<A> tmp = std::shared_ptr<A>(this);
        return tmp;
    }
}

int main()
{
   std::shared_ptr<A> sp1(new A);
   std::shared_ptr<A> sp2 = sp1->getA();
   return 0;
}

错误分析

两个shared_ptr sp1和sp2 互相不知道对方的存在, 双方都独立的own了A的同一个object, sp1和sp2 最终都会尝试释放对象, 导致问题。

解决方案

使用shared_from_this

参考资料

【1】https://blog.csdn.net/wqfhenanxc/article/details/80532931
【2】https://stackoverflow.com/questions/6892769/using-shared-ptr

6.2 使用一个裸指针初始化多个shared_ptr

示例代码

int *ptr = new int(10);
std::shared_ptr<int> p1(ptr);
std::shared_ptr<int> p2(ptr); //错误

错误分析

使用一个原始指针初始化多个shared_ptr, 多个shared_ptr互相不知道对方的存在,多个shared_ptr的独立维护自己的reference count, 每个shared_ptr在自己的reference count为0时都会尝试释放object, 导致多次释放同一个object。

6.3 循环引用

示例代码

#include <iostream>
#include <memory>
#include <string>
struct A;
struct B;

struct A
{
    std::shared_ptr<B> m_BPtr;
    ~A() { cout << "A is deleted!" << endl; };
};

struct B
{
    std::shared_ptr<A> m_APtr;
    ~B() { cout << "B is deleted!" << endl; };
};

void testPtr()
{
    std::shared_ptr<A> aptr(new A);
    std::shared_ptr<B> bptr(new B);
    aptr->m_BPtr = bptr;
    bptr->m_APtr = aptr;
}

错误分析

示例中A和B的object最终都不会被删除,存在内存泄漏。因为循环引用导致aptr和bptr的引用计数为2,在离开作用域后,引用计数均减为1不会减为0,导致两个指针不会被析构,导致内存泄露。

解决方案

可以使用weak_ptr解决这个问题,只需要将A或者B的任一个成员改成weak_ptr即可。

#include <iostream>
#include <memory>
#include <string>
struct A;
struct B;

struct A
{
    std::shared_ptr<B> m_BPtr;
    ~A() { cout << "A is deleted!" << endl; };
};

struct B
{
    std::weak_ptr<A> m_APtr;
    ~B() { cout << "B is deleted!" << endl; };
};

void testPtr()
{
    std::shared_ptr<A> aptr(new A);
    std::shared_ptr<B> bptr(new B);
    aptr->m_BPtr = bptr;
    bptr->m_APtr = aptr;
}

参考资料

【1】 https://stackoverflow.com/questions/24355804/how-to-properly-use-shared-ptr-in-good-c-apis
You are using shared_ptr to hold a back reference to the parent object whilst using one in the other direction. This will create a retain-cycle resulting in objects that never get deleted. In general, used a shared_ptr when referencing from the top down, and a weak_ptr when referencing up. Watch out in particular for delegate/callback/observer objects - these almost always want a weak_ptr to the callee. You also need to take care around lambdas if they are executing asynchronously. A common pattern is to capture a weak_ptr.

6.4 在函数实参中创建shared_ptr

示例代码

function(std::shared_ptr(new int), g()); //有缺陷

错误分析

因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也 可能从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还 没有创建, 则int内存泄漏了,正确的写法应该是先创建智能指针。

解决方案

std::shared_ptr p(new int); 
function(p, g());

参考资料

【1】https://blog.csdn.net/abcd552191868/article/details/122320741

七、shared_ptr定制删除器

shared_ptr’s custom deleter constrcutor. This was built to allow a shared pointer to take over a pointer from somewhere else and when it is destroyed, it will run your custom code instead of calling delete. In this case, you custom code will just do nothing since you don’t want to delete the pointer. That would look like

void foo(const MyClass* p) {
    std::shared_ptr<MyClass> wrapper;
    if (p == nullptr) {
        // Create a default object
        wrapper = std::make_shared<MyClass>("some", "parameters");
    } else {
        wrapper = std::shared_ptr(p, [](auto){ });
    }

    wrapper->doSomething();
    ...
    wrapper->doSomethingElse();
    // p mus not be destroyed, the default object (if created) shall be
}

参考:https://stackoverflow.com/questions/61480691/stdshared-ptr-which-is-empty-but-not-null

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
C++智能指针是一种用于管理动态分配的内存资源的工具。C++中提供了多种类型的智能指针,其中包括shared_ptr、unique_ptr和weak_ptr。这些智能指针都位于头文件<memory>中。 其中,shared_ptr是一种引用计数智能指针,它允许多个智能指针共享同一个对象。shared_ptr通过对对象的引用计数来管理内存的释放,当引用计数为0时,即没有智能指针指向该对象时,对象会被自动释放。使用shared_ptr需要包含头文件<memory>,并通过new关键字创建动态分配的对象并将其交给shared_ptr进行管理。 另一种智能指针是unique_ptr,它是一种独占型智能指针,只能有一个智能指针拥有对对象的所有权。当unique_ptr对象被销毁时,它所管理的对象也会被自动释放。unique_ptr提供了更高效的内存管理方式,因为它不需要进行引用计数。使用unique_ptr也需要包含头文件<memory>,并使用new关键字创建动态分配的对象并将其交给unique_ptr进行管理。 除了shared_ptr和unique_ptr,还有其他类型的智能指针,如weak_ptr和scoped_ptr。weak_ptr是一种弱引用智能指针,它用于解决shared_ptr可能出现的循环引用问题。scoped_ptr是一种简单的智能指针,它只能在创建时初始化,并且不能进行复制和赋值操作。scoped_ptr在其所属的作用域结束时自动释放所管理的对象。 总结起来,C++智能指针是一种用于管理动态分配的内存资源的工具,包括shared_ptr、unique_ptr、weak_ptr和scoped_ptr等类型。它们提供了一种更安全、更高效的内存管理方式,避免了手动释放内存的麻烦。要使用这些智能指针,需要包含头文件<memory>,并通过new关键字创建动态分配的对象并将其交给相应的智能指针进行管理。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [C++ -- 智能指针C++11与boost库的智能指针及其使用)](https://blog.csdn.net/xu1105775448/article/details/80625936)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [C++智能指针的底层实现原理](https://blog.csdn.net/ArtAndLife/article/details/120793343)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值