【shared_ptr】智能指针shared_ptr的实现及用法

概述

要确保用new动态分配的内存空间再程序各条执行路径都能被释放是一件麻烦的事情。C++11模板库的头文件中定义的智能指针,std::shared_ptr模板,就可以用来部分解决这个问题。

只要将new运算符返回的指针p交给一个shared_ptr对象“托管”,就不用担心在哪里写delete p语句,托管p的shared_ptr对象在消亡时会自动执行delete p。而且shared_ptr对象能像指针p一样使用。假设托管p的shared_ptr对象叫做ptr,那么*ptr就是p指向的对象。

shared_ptr是最像指针的智能指针,在很多的组件中被应用,shared_ptr包装了new操作符在堆上分配的动态对象,实现的是引用计数型的智能指针,可以被自由的拷贝和赋值,当没有代码使用(引用计数减为0)时会删除包装的动态分配的对象。

shared_ptr也可以安全地放在标准的容器中,是在STL容器中存储指针的最标准解法。

原理

智能指针是模板类而不是指针。创建一个智能指针时,指针必须指向数据类型,,

智能指针实质是重载了->和*操作符的类,由类来实现对内存的管理,确保及时有异常产生,也可以通过智能指针类的析构函数完成内存的释放。

它利用了引用计数技术和C++的RAII(资源获取就是初始化)特性。RAII可以保证在任何情况下,使用对象时先构造对象,最后析构对象。引用计数是通过计算对裸指针引用次数才决定是否要释放掉这个指针对象。

把shared_ptr设置为nullptr就可以让shared_ptr去释放所管理的裸指针。

类摘要

template <class T>
class shared_ptr
{
public:
    typedef T element_type;          // 内部类型定义

    shared_ptr();                    // 构造函数
    template<class Y> explicit shared_ptr(Y * p);
    template<class Y,class D> shared_ptr(Y * p,D d);

    ~shared_ptr();                  // 析构函数
    shared_ptr(shared_ptr const & r);   // 拷贝构造
    
    shared_ptr & operator = (shared_ptr const & r);    // 赋值操作
    template<class Y> shared_ptr & operator=(shared_ptr<Y> const & r);

    void reset();                       // 重置智能指针
    template<class Y> void reset(Y * p);
    template<class Y,class D> void reset(Y * p,D d);

    T & operator*() const;              // 操作符重载
    T & operator->() const;             // 操作符重载
    T * get() const;                    // 获得原始指针

    bool unique() const;                // 是否唯一
    long use_count() const;             // 引用计数

    explicit operator bool() const;     // 显式bool型转换
    void swap(shared_ptr & b);          // 交换指针

};

基本用法

shared_ptr重载了“*”和"->"操作符以模仿原始指针的行为,提供显示bool类型转换以判断指针的有效性,get()可以得到原始指针,并且没有提供指针算术操作,也不能管理new[]产生的动态数组指针

shared_ptr<T>  ptr(new T);                                // T可以是int、char、class等各种类型
shared_ptr<std::string>  sps(new std::string("smart"));   // 一个string的shared_ptr
assert(sps->size() == 5);                                 // 使用箭头操作符->

shared_ptr<int> dont_do_this(new int[10]);                // 错误用法,不能正确释放内存

增加计数

被引用会增加计数

std::shared_ptr<int> ptr2(sp2);      // 再次被引用则计数+1

在函数内改变计数,超过生命周期后计数会回复,test函数内的p1在函数返回后被析构

void test(int* ptr)
{
    std::shared_ptr<int> p1(ptr);
    int n = p1.use_count();
    std::cout << n << std::endl;
}

得到原指针

get()函数返回原指针

int *n3 = sp1.get();
std::cout<<*(sp2.get()) << std::endl;

性能说明

尺寸大小

#include <iostream>
#include <memory>
using namespace std;
int main() {
    char *p;
    int lenp = sizeof(p); // 4字节
    cout << lenp << endl;
    shared_ptr<string> p1;
    int ilensp = sizeof(p1);
    cout << ilensp << endl; // 8字节,包含两个裸指针
    return 0;
}

shared_ptr的尺寸是裸指针的2倍,weak_ptr尺寸是裸指针的2倍

控制块的创建时机:

1.make_shared:分配并初始化一个对象,返回指向对象的shared_ptr,所以,这个make_shared它总是能够创建一个控制块

shared_ptr<int> p2 = make_shared<int>(100);

2.用裸指针来创建一个shared_ptr对象

int*p = new int();
shared_ptr<int> p1(p);

移动语义

shared_ptr<int> p1(new int(100));
shared_ptr<int> p2(std::move(p1));  // 移动语义,移动构造一个新的智能指针p2
                                 // p1就不再指向该对象(变成空),引用计数依旧是1
shared_ptr<int> p3;
p3 = std::move(p2);              // 移动赋值,p2指向空, p3指向该对象,整个对象的引用计数仍旧为1

移动比复制速度要快,移动构造函数也快过复制构造函数

实例代码

#include <iostream>
#include <memory>

using namespace std;

class A
{
public:
    int i;
    A(int n):i(n) { };
    ~A() {cout<<i<<" "<<"destructed"<<endl;}

};

int main()
{
    shared_ptr<A> sp1(new A(2));     // A(2)由sp1托管
    shared_ptr<A> sp2(sp1);          // A(2)同时交给sp2托管
    shared_ptr<A> sp3;
    sp3 = sp2;           // A(2)同时交由sp3托管
    cout << sp1->i << "," << sp2->i <<"," << sp3->i << endl;
    A* p = sp3.get();
    cout<<p->i<<endl;   // 输出2
    sp1.reset(new A(3));     // reset导致托管新的指针,此时sp1托管A(3)
    sp2.reset(new A(4));     // 时sp2托管A(4)
    cout << sp1->i << endl;  // 输出 3
    sp3.reset(new A(5));     // sp3托管A(5),A(2)无人托管,被delete
    cout << "end" << endl;
    return 0;
}

运行结果:

2,2,2
2
3
2 destructed
end
5 destructed
4 destructed
3 destructed

可以用shared_ptr< A > sp2(sp1);和sp3 = sp2;的形式让多个sharecLptr对象托管同一个指针。这多个shared_ptr对象会共享一个对共同托管的"托管计数"。有n个shared_ptr对象托管同一个指针p,则p的托管计数就是n。当一个指针托管计数为0时,指针会被释放。

只有指向动态分配的对象的指针才能交给shared_ptr对象托管。将指向普通局部变量、全局变量的指针交给shared_ptr托管,编译时不会有问题,但程序运行时会报错,因为不能析构一个并没有指向动态分配的内存空间的指针。

参考文章:
https://blog.csdn.net/ingnight/article/details/99881762
https://blog.csdn.net/bandaoyu/article/details/107133606

原文链接: https://www.cnblogs.com/Wangzx000/p/16596769.html

欢迎关注

微信关注下方公众号,第一时间获取干货硬货;公众号内回复【pdf】免费获取数百本计算机经典书籍

    【shared_ptr】智能指针shared_ptr的实现及用法

原创文章受到原创版权保护。转载请注明出处:https://www.ccppcoding.com/archives/310363

非原创文章文中已经注明原地址,如有侵权,联系删除

关注公众号【高性能架构探索】,第一时间获取最新文章

转载文章受原作者版权保护。转载请注明原作者出处!

(0)
上一篇 2023年2月16日 上午11:29
下一篇 2023年2月16日 上午11:32

相关推荐