智能指针梳理

RAII

RAII(Resource Acquisition Is Initialization)资源获取即初始化。

它是一种利用对象生命周期来控制程序资源(如内存、文件句柄、网络连接、互斥量等等)的简单技术。

在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源。至此我们可以看到实际上就是把一份资源的责任托管给了一个对象。

这种做法有两大好处:

  1. 不需要显式释放资源。析构时自动调用。

  2. 采用这种方式,对象所需的资源在其生命期内始终保持有效。

智能指针是RAII思想的一种具像

c++里面的四个智能指针: auto_ptr, shared_ptr, weak_ptr, unique_ptr 其中后三个是c++11支持,并且第一个已经被c++11弃用

一、shared_ptr

std::shared_ptr也即智能指针,采用RAII手法,是一个模版对象。std::shared_ptr表示某一个资源的共享所有权。

线程安全

shared_ptr指针类有两个成员变量,一个是指向变量的指针;一个是资源被引用的次数,引用次数加减操作内部自动加锁解锁,是线程安全的。

指针和引用计数是线程安全的,但指针所指对象中的操作就需要自己做控制,并不是线程安全的。因为shared_ptr 有两个数据成员(指向被管理对象的指针,和指向控制块的指针),读写操作不能原子化。

线程安全case1

多线程读写shared_ptr所指向的同一个对象,不管是相同的shared_ptr对象,还是不同的shared_ptr对象,也需要加锁保护

shared_ptr<long> global_instance = make_shared<long>(0);
std::mutex g_i_mutex;
 
void thread_fcn()
{
    //std::lock_guard<std::mutex> lock(g_i_mutex);
 
    //shared_ptr<long> local = global_instance;
 
    for(int i = 0; i < 100000000; i++)
    {
        *global_instance = *global_instance + 1;
        //*local = *local + 1;
    }
}
 
int main(int argc, char** argv)
{
    thread thread1(thread_fcn);
    thread thread2(thread_fcn);
 
    thread1.join();
    thread2.join();
 
    cout << "*global_instance is " << *global_instance << endl;
 
    return 0;
}

在线程函数thread_fcn的for循环中,2个线程同时对*global_instance进行加1的操作。这就是典型的非线程安全的场景,最后的结果是未定的,运行结果如下:

*global_instance is 197240539

如果使用的是每个线程的局部shared_ptr对象local,因为这些local指向相同的对象,因此结果也是未定的,运行结果如下:

*global_instance is 160285803

因此,这种情况下必须加锁,将thread_fcn中的第一行代码的注释去掉之后,不管是使用global_instance,还是使用local,得到的结果都是:

*global_instance is 200000000

线程安全case 2

• 一个 shared_ptr 对象实体可被多个线程同时读取(文档例1);

• 两个 shared_ptr 对象实体可以被两个线程同时写入(例2),“析构”算写操作;

• 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁(例3~5)。

请注意,以上是 shared_ptr 对象本身的线程安全级别,不是它管理的对象的线程安全级别。

shared_ptr<Foo> g(new Foo); // 线程之间共享的 shared_ptr

shared_ptr<Foo> x; // 线程 A 的局部变量

shared_ptr<Foo> n(new Foo); // 线程 B 的局部变量

C++ shared_ptr的线程安全问题_c++ shared_ptr 线程安全-CSDN博客

https://www.cnblogs.com/gqtcgq/p/7492772.html

shared_ptr的API使用

函数API含义
p.get()返回p中保存的指针。小心使用,若智能指针释放了其对象,返回的指针所指向的对象也就消失了
p.use_count()返回与p共享对象的智能指针数量;可能很慢,注意用于调试
p.unique()若p.use_count()为1,返回true;否则返回false
p = qp和q都是shared_ptr,所保存的指针能相互转换。此操作会递减p的引用计数,递增q的引用计数;若p的引用计数变为0,则将其管理的原内存释放
make_shared<T>(args)返回一个shared_ptr,指向一个动态分配的类型为T的对象。使用args初始化此对象
shared_ptr<T> p(q)p是share_ptr q的拷贝;此操作会递增q中的计数器。q中的指针类型必须能转换为T*
shared_ptr<T> p(u)p从unique_ptr u那里接管了对象的所有权;将u置为空
shared_ptr<T> p(q,d)p接管了内置指针q所指向的对象的所有权。q必须能转换为T*类型。p将使用可调用的对象d来代替delete
shared_ptr<T> p(p2,d)p是share_ptr p2的拷贝;此操作会递增p2中的计数器。p2中的指针类型必须能转换为T*,p将使用可调用的对象d来代替delete
p.reset()p置为空。若p是唯一指向其对象的shared_ptr,reset会释放此对象
p.reset(q)传递了参数内置指针q,会令p指向q
p.reset(q,d)传递了参数d,将会调用d而不是delete来释放q
p.swap(q)或swap(p,q)交换两个智能指针

可以通过如下两种方式创建std::shared_ptr对象

auto p = std::shared_ptr<T>(new T);
auto p = std::make_shared<T>(T{});

shared_ptr的拷贝构造函数会增加引用计数,而移动构造函数不会增加引用计数。

别名构造函数

template <class U> shared_ptr (const shared_ptr<U>& x, element_type* p) noexcept;  除了存储的指针是p。该对象不拥有 p,也不会管理其存储。相反,它共同拥有 x 的托管对象并算作 x 的一种额外使用。它还将在发布时删除 x 的指针(而不是 p)。它可以用来指向已经被管理的对象的成员。

why make_shared ? - Bitdewy

https://www.cnblogs.com/chaohacker/p/14802112.html

Make_shared

优点

        效率更高

        异常安全

缺点

        构造函数是保护或私有时,无法使用 make_shared

        对象的内存可能无法及时回收

二、unique_ptr

1、如何创建unique_ptr

unique_ptr不像shared_ptr一样拥有标准库函数make_shared来创建一个shared_ptr实例。要想创建一个unique_ptr,我们需要将一个new 操作符返回的指针传递给unique_ptr的构造函数。

2、无法进行复制构造和赋值操作

unique_ptr没有copy构造函数,不支持普通的拷贝和赋值操作。

3、可以进行移动构造和移动赋值操作

unique_ptr虽然没有支持普通的拷贝和赋值操作,但却提供了一种移动机制来将指针的所有权从一个unique_ptr转移给另一个unique_ptr。如果需要转移所有权,可以使用std::move()函数。

4、可以返回unique_ptr

unique_ptr不支持拷贝操作,但却有一个例外:可以从函数中返回一个unique_ptr。

int main() 
{
    // 创建一个unique_ptr实例
    unique_ptr<int> pInt(new int(5));
    unique_ptr<int> pInt2(pInt);    // 报错
    unique_ptr<int> pInt3 = pInt;   // 报错
}

int main() 
{
    unique_ptr<int> pInt(new int(5));
    unique_ptr<int> pInt2 = std::move(pInt);    // 转移所有权
    //cout << *pInt << endl; // 出错,pInt为空
    cout << *pInt2 << endl;
    unique_ptr<int> pInt3(std::move(pInt2));
}


unique_ptr<int> clone(int p)
{
    unique_ptr<int> pInt(new int(p));
    return pInt;    // 返回unique_ptr
}

int main() {
    int p = 5;
    unique_ptr<int> ret = clone(p);
    cout << *ret << endl;
}

 C++ 智能指针与底层实现剖析 - 掘金

深入理解Modern C++智能指针std::shared_ptr - 知乎

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值