C++之shared_ptr总结

本文深入探讨了C++中的智能指针shared_ptr的工作原理及其使用细节,对比了auto_ptr的特点,介绍了shared_ptr如何解决对象所有权共享问题,并适用于容器中。文章还详细解析了shared_ptr的构造函数、析构函数、成员函数等,并提供了简单的源码实现。

Share_ptr也是一种智能指针。类比于auto_ptr学习。所以推荐先学习auto_ptr,再来学习shared_ptr。本博客的前两个就是auto_ptr的总结。希望感兴趣的朋友可以看看。

 

Shared_ptr和auto_ptr最大的区别就是,shared_ptr解决了指针间共享对象所有权的问题,也就是auto_ptr中的赋值的奇怪问题。所以满足了容器的要求,可以用于容器中。而auto_ptr显然禁止共享对象所有权,不可以用于容器中。

       int * a=new int(2);
       shared_ptr<int> sp1(a);
       shared_ptr<int> sp2(sp1);     OK

当然shared_ptr作为一种智能指针,也拥有和shared_ptr一些相似的性质。它们本质上都是类,但是使用起来像指针。它们都是为了解决防止内存泄漏的解决方案。都是运用了RAII技术来实现的。

 

注意:使用shared_ptr也要引用头文件#include<memory>

 

由于shared_ptr的源码过于复杂,我们不给出源码。类比于auto_ptr学习.

 

1. 首先类shared_ptr有两个成员变量。T * px和unsign long * pn;

T * px;显然和auto_ptr一样,用于储存对象的指针。

pn用于记录有多少个shared_ptr拥有同一个对象。pn是shared_ptr对象间共享的,类似于static成员变量。 

template<class T>
class shared_ptr{
private:
       T *px; // contained pointer
    unsignedlong* pn; // reference counter
}

总结:其实shared_ptr的原理,就是使用px来记录指针,使用*pn来记录px指向的对象的拥有者share_ptr的个数,当一个shared_ptr对象达到作用域时,不会释放资源,只有当*pn变为0的时候,才会释放指针指向的资源。

 

2. 一个简单实现的源码(仍然看懂源码还是最重要的。

#pragma once
//shared_ptr的简单实现版本
//基于引用记数的智能指针
//它可以和stl容器完美的配合
namespace boost
{
template<class T>
class shared_ptr
{
typedef unsigned longsize_type;
private:
       T *px; // contained pointer
   size_type* pn; // reference counter
public:
//构造函数---------------------------------------------------2
/*
int* a=new int(2);
shared_ptr<int> sp;
shared_ptr<int> sp(a);
*/
explicitshared_ptr(T* p=0) : px(p)
{
   pn = new size_type(1);
}
 
/*
Derived d;
shared_ptr<Base> ap(d);
*/
template<typename Y>
shared_ptr(Y* py)
{
pn = newsize_type(1);
px=py;
}
//copy构造函数------------------------------------------------
/*
int * a=new int;
shared_ptr<int> sp(a);
shared_ptr<int> sp1(sp);
*/
shared_ptr(constshared_ptr& r) throw(): px(r.px)
{
++*r.pn;
pn = r.pn;
}
 
/*
shared_ptr<Derived>sp1(derived);
shared_ptr<Base> sp2(sp1);
*/
template<typename Y>
shared_ptr(constshared_ptr<Y>& r)//用于多态
{
px = r.px;
++*r.pn;
pn = r.pn; //shared_count::op= doesn't throw
}
//重载赋值operator=--------------------------------------------
shared_ptr& operator=(const shared_ptr& r) throw()
{
if(this== &r) return *this;
dispose();
px = r.px;
++*r.pn;
pn = r.pn;
return *this;
}
template<typename Y>
shared_ptr& operator=(const shared_ptr<Y>& r)//用于多态
{
dispose();
px = r.px;
++*r.pn;
pn = r.pn; //shared_count::op= doesn't throw
return *this;
}
 
~shared_ptr() { dispose(); }
void reset(T* p=0)
{
if ( px == p ) return;
if (--*pn == 0)
{ delete(px); }
else
{ // allocate newreference
// counter
// fix: prevent leak if new throws
try { pn = new size_type; }
catch (...) {
// undo effect of —*pn above to
// meet effects guarantee
++*pn;
delete(p);
throw;
} // catch
} // allocate newreference counter
*pn = 1;
px = p;
} // reset
reference operator*()const throw(){ return *px; }
pointer operator->()const throw(){ return px; }
pointer get() constthrow(){ returnpx; }
size_type use_count() constthrow()//
{ return *pn; }
bool unique() const throw()//
{ return *pn ==1; }
private:
void dispose() throw()
{
if (--*pn == 0)
{ delete px; delete pn; }
}
}; // shared_ptr
template<typename A,typenameB>
inline bool operator==(shared_ptr<A>const & l, shared_ptr<B> const & r)
{
return l.get() == r.get();
}
template<typename A,typenameB>
inline bool operator!=(shared_ptr<A>const & l, shared_ptr<B> const & r)
{
return l.get() != r.get();
}
}//namespace boost


要注意的地方:

 

3. Shared_ptr和auto_ptr都有类似的规定:

看看它们的copy构造和重载赋值都可以看出:

不允许

       int* a=new int(2);
       shared_ptr<int>sp=a;//  error
       sp=a;//    error

就是不允许使用一个纯指针给一个智能指针赋值或copy构造。只能使用智能指针给另一个智能指针赋值或copy构造。

       int* a=new int(2);
   shared_ptr<int> sp(a);//构造函数
    shared_ptr<int> sp1(sp);//copy构造
       sp1=sp;//赋值

在auto_ptr中也是相同的。

 

4. 注意shared_ptr的几个函数

Ø     Reset()函数:重置函数

标准中的是:

      int* a=new int(2);
       int* b=new int(3);
    shared_ptr<int> sp2(a);
    shared_ptr<int> sp1(a);
   shared_ptr<int> sp(a);
       sp.reset(b);
      sp.reset();
      sp.reset(sp2);  -----!!!也是可以的。

使得sp获得b的拥有权。失去a的拥有权。注意这会使得a的拥有者少1.当a的拥有者变为0时,就会释放a的资源。

Ø     Swap()函数:交换函数

       int* a=new int(2);
       shared_ptr<int> sp(a);
       shared_ptr<int> sp1(a);
       sp.swap(sp1);

就是两个shared_ptr中的px和pn都互换一下。

Ø     Get()函数:返回px

Ø     Use_count函数:返回*pn,就是对象的拥有者的数量。

Ø     Unique函数:令*pn=1;让对象的拥有者的数量变为1。返回bool

Ø     同时share_ptr也重载了*和->

 

5. tr1中重载了几个有关shared_ptr的符号:

template<classT, class U>

booloperator==(shared_ptr<T> const& a, shared_ptr<U> const& b);

判断拥有的对象是否是一样的

 

template<classT, class U>

 bool operator!=(shared_ptr<T> const&a, shared_ptr<U> const& b);

判断拥有的对象是否是不一样的

 

template<classT, class U>

 bool operator<(shared_ptr<T>const& a, shared_ptr<U> const& b);

重载了小于号,在STL中的LIST中非常有用。

       int* a=new int(2);
       int* b=new int(3);
       shared_ptr<int> sp(a);
       shared_ptr<int> sp1(b);
       if(sp<sp1)
              cout<<"2222"<<endl;

6. 注意真实中shared_ptr中没有public dispose这个函数,这里只是为了避免代码重复。

 

7. 注意shared_ptr中的析构函数中不是直接释放资源,而是调用了dispose函数,如果*pn==0了,才会释放资源。

 

8.shared_ptr的多线程的安全性

shared_ptr 本身不是 100%线程安全的。它的引用计数本身是安全且无锁的,但对象的读写则不是,因为shared_ptr有两个数据成员,读写操作不能原子化。根据文档shared_ptr的线程安全级别和内建类型、标准库容器、string一样,即:

  • 一个 shared_ptr 实体可被多个线程同时读取;
  • 两个的 shared_ptr 实体可以被两个线程同时写入,“析构”算写操作;
  • 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁。

 

发现了两个非常有意思的东西:

1. 看tr1中的源码中发现两个这样的东西:

template<class Y, classD> shared_ptr(Y * p, D d);

template<class Y, classD> void reset(Y * p, D d);

其中的D d是个什么东西?源码的解释是d是一个deleter(删除器)。至此我们突然发现我们可以给shared_ptr指定一个删除器,当*pn==0的时候,不去释放资源,而去调用我们自己给它的删除器。

 

当shared_ptr的引用次数为0的时候,share_ptr就会调用释放函数来释放资源。

当我们希望引用次数为0的时候,shared_ptr不释放资源,而是调用我们指定的操作的时候,就会用到D d;

void foo(int * d)
{
       cout<<"1234"<<endl;
}
 
int _tmain(int argc, _TCHAR* argv[])
{
       int* a=new int(2);
       shared_ptr<int> sp(a,foo);
       shared_ptr<int> sp1(sp);
       sp.reset();
       sp1.reset();
       //_CrtDumpMemoryLeaks();
       system("pause");
       return 0;
}


注意!:

1. 指定的删除器的参数必须是int*;和shared_ptr<int>中的int对应。不能是其他的,或者为空也是错的。因为系统会把shared_ptr的对象px赋给删除器的参数,我们也可以在删除器中释放资源。

2. 只有a的引用次数为0才会调用,所以如果没有sp1.reset()。也不会调用foo函数。

 

2. 使用shared_ptr的时候,要小心,想一想操作的内在含义才去做。

1>

       int* a=new int(2);
       shared_ptr<int> sp(a);
       shared_ptr<int> sp1(sp);
       sp.reset();//--------(1)
       sp.reset();//--------(2)


这里(1)是重置了sp,注意(2)是没有任何作用的,不能使得a的引用次数变为0.想一想reset的函数内部,(2)的时候,sp中的对象pn已经为空了,则不能改变*pn的值了。

 

2>

     int* a=new int(2);
       shared_ptr<int> sp(a);//----------(1)
       shared_ptr<int> sp1(a);//---------(2)

注意:这里的(2)也是不对的。想一想shared_ptr的构造函数,(1)的时候,sp的px指向a,且*pn为1.而(2)的时候,px指向a,且*pn也是1.这显然就问题了。a被引用了2次,但是*pn为1.在最后作用域达到的时候,就会释放2次内存,这就会引发异常。

 

总结:shared_ptr和auto_ptr的区别。

Shared_ptr有两个变量,一个记录对象地址,一个记录引用次数

Auto_ptr只有一个变量,用来记录对象地址

 

Shared_ptr可用多个shared_ptr拥有一个资源。

Auto_ptr只能一个auto_ptr拥有一个资源

 

Shared_ptr可以实现赋值的正常操作,使得两个地址指向同一资源

Auto_ptr的赋值很奇怪,源失去资源拥有权,目标获取资源拥有权

 

Shared_ptr到达作用域时,不一定会释放资源。

Auto_ptr到达作用于时,一定会释放资源。

 

Shared_ptr存在多线程的安全性问题,而auto_ptr没有。

 

Shared_ptr可用于容器中,而auto_ptr一般不可以用于容器中。


Shared_ptr可以在构造函数、reset函数的时候允许指定删除器。而auto_ptr不能。

 

还有这里说一句:使用智能指针(不管shared_ptr还是auto_ptr),都要清除源码内部的实现原理,使用起来才不会错。而且使用的时候,一定要想一想函数内部的实现原理再去使用。切记小心。


<think> 我们被要求介绍C++shared_ptr的用法。根据引用内容,shared_ptrC++11中引入的智能指针,用于自动管理内存,避免内存泄漏。下面将详细介绍其用法。 主要知识点: 1. shared_ptr的基本概念:共享所有权,使用引用计数机制。 2. 初始化shared_ptr的多种方式。 3. shared_ptr的常用成员函数。 4. 使用shared_ptr的注意事项。 下面分点详细说明: ### 一、shared_ptr的基本概念 `shared_ptr`是一种智能指针,它记录着多个指针指向同一个对象的引用计数。当最后一个指向对象的`shared_ptr`被销毁时,对象会被自动删除。这避免了手动调用`delete`,从而减少了内存泄漏的风险。 ### 二、初始化shared_ptr #### 1. 使用构造函数 ```cpp std::shared_ptr<int> sp1(new int(10)); // 正确:直接初始化 ``` #### 2. 使用make_shared函数(推荐) ```cpp std::shared_ptr<int> sp2 = std::make_shared<int>(10); // 更高效 ``` 使用`std::make_shared`在动态分配内存的同时创建控制块(存储引用计数),通常效率更高,且能保证异常安全。 #### 3. 错误初始化方式 ```cpp std::shared_ptr<int> sp3 = new int(10); // 错误:构造函数是explicit的 ``` ### 三、shared_ptr的常用操作 #### 1. 获取原始指针 ```cpp int* raw_ptr = sp1.get(); // 获取原始指针,但不增加引用计数 ``` 注意:不要手动删除该指针,也不要用它创建另一个shared_ptr,否则可能导致重复释放。 #### 2. 重置指针 ```cpp sp1.reset(); // 释放所管理的对象,sp1变为空 sp1.reset(new int(20)); // 释放原对象,管理新对象 ``` #### 3. 查看引用计数 ```cpp long count = sp1.use_count(); // 返回引用计数(注意:在多线程环境下可能不准确) ``` ### 四、shared_ptr的拷贝与赋值 拷贝`shared_ptr`会增加引用计数: ```cpp std::shared_ptr<int> sp4 = sp1; // 引用计数加1 ``` ### 五、自定义删除器 当对象不是通过`new`创建,或者需要特殊删除操作时,可以指定自定义删除器: ```cpp void my_deleter(int* p) { delete p; } std::shared_ptr<int> sp5(new int(30), my_deleter); // 或者使用lambda表达式 std::shared_ptr<int> sp6(new int(40), [](int* p){ delete p; }); ``` ### 六、注意事项 1. **不要用同一个原始指针初始化多个shared_ptr**: ```cpp int* p = new int(50); std::shared_ptr<int> sp7(p); std::shared_ptr<int> sp8(p); // 错误:会导致重复释放 ``` 2. **避免循环引用**:如果两个对象互相持有对方的`shared_ptr`,会导致引用计数永远不为0,从而内存泄漏。解决方案:使用`weak_ptr`打破循环。 3. **避免使用get()返回的指针创建shared_ptr**:这会导致多个控制块,同样会有重复释放的风险。 4. **多线程安全性**:`shared_ptr`的引用计数本身是原子操作,线程安全。但其所指向的对象则不一定安全,需要额外保护。 ### 七、示例代码 ```cpp #include <memory> #include <iostream> class MyClass { public: MyClass() { std::cout << "MyClass constructed\n"; } ~MyClass() { std::cout << "MyClass destroyed\n"; } }; int main() { // 使用make_shared创建 std::shared_ptr<MyClass> sp1 = std::make_shared<MyClass>(); { // 拷贝构造,引用计数增加 std::shared_ptr<MyClass> sp2 = sp1; std::cout << "Reference count: " << sp1.use_count() << std::endl; // 输出2 } // sp2离开作用域,引用计数减1 std::cout << "Reference count: " << sp1.use_count() << std::endl; // 输出1 return 0; } // sp1离开作用域,引用计数为0,对象销毁 ``` 输出: ``` MyClass constructed Reference count: 2 Reference count: 1 MyClass destroyed ``` ### 八、总结 `shared_ptr`是C++中管理动态内存的重要工具,它通过引用计数实现自动内存管理。使用时应注意初始化方式(推荐`make_shared`),避免循环引用和重复管理同一原始指针。在多线程环境中,要确保对共享对象的访问安全[^1][^2][^3]。 §§相关问题§§ 1. 什么是C++中的weak_ptr?它如何解决循环引用问题? 2. shared_ptr和unique_ptr有什么区别?各自适用什么场景? 3. 如何用shared_ptr管理数组? 4. shared_ptr的线程安全性如何保证? 5. 自定义删除器在实际开发中有哪些应用场景? </think> ### C++shared_ptr用法详解 `shared_ptr`是C++11引入的智能指针,用于**自动管理动态内存**,通过**引用计数机制**实现多指针共享对象所有权。当最后一个`shared_ptr`销毁时,自动释放内存,避免内存泄漏[^1][^2][^3]。 --- #### 一、基本用法 ##### 1. 初始化方式 ```cpp // 原始指针构造(不推荐,有安全隐患) std::shared_ptr<int> sp1(new int(42)); // make_shared构造(推荐,更高效安全) auto sp2 = std::make_shared<std::string>("Hello"); // 拷贝构造(引用计数+1) auto sp3 = sp2; ``` ##### 2. 关键操作 ```cpp sp1.use_count(); // 获取引用计数:1 sp2.reset(); // 释放对象所有权(引用计数-1) sp1.get(); // 获取原始指针(谨慎使用) ``` --- #### 二、核心特性 1. **引用计数机制** ```mermaid graph LR A[shared_ptr1] --> C[对象X] B[shared_ptr2] --> C C --> D[引用计数=2] ``` 当最后一个`shared_ptr`离开作用域时,对象自动销毁。 2. **线程安全** 引用计数操作是原子的,但指向的对象访问需要额外同步[^3]。 3. **自定义删除器** ```cpp // 文件指针的自定义删除 auto file_deleter = [](FILE* f) { if(f) fclose(f); }; std::shared_ptr<FILE> fp(fopen("data.txt", "r"), file_deleter); ``` --- #### 三、使用场景 1. **共享对象所有权** ```cpp class NetworkConnection { public: void send(const std::string& data); }; auto conn = std::make_shared<NetworkConnection>(); auto worker1 = [conn] { conn->send("data1"); }; auto worker2 = [conn] { conn->send("data2"); }; // 两个线程共享同一个连接对象 ``` 2. **容器存储指针** ```cpp std::vector<std::shared_ptr<Shape>> shapes; shapes.push_back(std::make_shared<Circle>()); shapes.push_back(std::make_shared<Rectangle>()); ``` --- #### 四、注意事项 1. **禁止混用原始指针** ```cpp int* raw = new int(100); std::shared_ptr<int> p1(raw); std::shared_ptr<int> p2(raw); // 错误!会导致双重释放 ``` 2. **避免循环引用** 使用`weak_ptr`打破循环: ```cpp struct Node { std::shared_ptr<Node> next; std::weak_ptr<Node> prev; // 弱引用避免循环 }; ``` 3. **性能考虑** - `make_shared`比`new`高效(单次内存分配) - 引用计数带来约10-20%性能开销[^1] --- #### 五、与unique_ptr对比 | 特性 | shared_ptr | unique_ptr | |------|------------|------------| | 所有权 | 共享 | 独占 | | 拷贝语义 | ✅ 允许 | ❌ 禁止 | | 移动语义 | ✅ | ✅ | | 自定义删除器 | ✅ | ✅ | | 内存开销 | 控制块(约16-32字节) | 无额外开销 | > 优先使用`unique_ptr`,仅在需要共享所有权时用`shared_ptr`[^3]。 --- ### 总结 `shared_ptr`是C++内存管理的核心工具,通过: 1. 使用`make_shared`初始化 ✅ 2. 避免原始指针混用 ✅ 3. 用`weak_ptr`解决循环引用 ✅ 4. 了解性能开销 ✅ 可显著提升代码安全性和可维护性[^1][^2][^3]。
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值