欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

C++ shared_ptr用法、简析、案例

程序员文章站 2024-03-14 11:37:28
...

1 shared_ptr简介

shared_ptr内容特别多,我希望可以讲清楚,但是并不能遍布所有。先看看思维导图,
作为最智能的内存管理成员–shared_ptr,shared_ptr早已经加入C++11的标准,下面的内容在Boost和C++标准中都是一致的。
本文分如下要点:

1. 创建:shared_ptr支持所有能想到的操作,不管是拷贝构造、赋值、显示构造、别名构造等。对比scoped_ptr、unique_ptr是不支持拷贝构造、赋值的(unique_ptr有一个右值赋值)
2. 指针操作:支持*、->、获取原始指针get()。对比weak_ptr不支持*和->
3. 比较操作:支持==、<、<<(流操作),注意这里的==是任意两个shared_ptr比较操作。对比scoped_ptr、unique_ptr只支持和空指针比较,就只有shared_ptr有<比较操作----应用在关联容器
4. 简单的工厂函数,目的更好的使用shared_ptr
5. 最重要的是,应用案例,也是本文的核心。

C++ shared_ptr用法、简析、案例

2 shared_ptr案例

下面是基础理解,一些简单的API应用

class shared
{
private:
    boost::shared_ptr<int> m_p;

public:
    // 拷贝方式
    shared(boost::shared_ptr<int> p) : m_p(p){};
    ~shared(){};

    void print()
    {
        std::cout << "count: " << m_p.use_count() << " v= " << *m_p << std::endl;
    }
};

// 拷贝方式 又拷贝了一次,紧接着又释放了
void print_fun(boost::shared_ptr<int> p)
{
    std::cout << "count: " << p.use_count() << " v= " << *p << std::endl;
}

{
	// main
    boost::shared_ptr<int> p(new int(100));
    shared s1(p), s2(p);		// 拷贝了两次

    s1.print();
    s2.print();

    *p = 20;
    print_fun(p);				

    s1.print();
    s2.print();
}

答案:

count: 3 v= 100
count: 3 v= 100
count: 4 v= 20
count: 3 v= 20
count: 3 v= 20

从结果,可以看出,不要做一些无畏的拷贝,const &!!!
任何一个参数,先把const &加上,问一下两个问题

  1. 我需要拷贝吗?需要就去掉&
  2. 我需要修改值吗?需要去掉const

2.1 reset()的理解

reset()也是一族函数,本意是即将管理另外一个指针p,放弃当前的。
如果p为空,管理另外一个指针p(管理空指针),放弃当前的。相当于置0。
注意:

  1. p只是原始指针,并不是智能指针!!!
  2. 从原理上讲,放弃原来的—计数变量会减1;管理新的—又会加1,有时候会发现不变,抵消了
void reset() BOOST_SP_NOEXCEPT
template<class Y> void reset( Y * p )
template<class Y, class D> void reset( Y * p, D d )
...

分析下面的代码

    {
        auto sp = boost::make_shared<std::string>("make_shared123");
        auto sp1 = new std::string("xxxx");
        std::cout << "use_count: " << sp.use_count() << std::endl;
        sp.reset(sp1);
        std::cout << "use_count: " << sp.use_count() << std::endl;
        std::cout << "*sp: " << *sp << std::endl;
    }

输出:
C++ shared_ptr用法、简析、案例

  1. sp管理”make_shared123“,引用计数加1
  2. 之后sp管理新的”xxxx“,相当于原来的已经释放了,计数器先减1,后增加1,计数器值保持不变

2.2 make_shared的理解

通常make_shared()函数比直接创建shared_ptr对象要高效,且只分配一次内存

  1. 方式一,直接使用make_shared效率更高,不仅快而且高效,分配一次内存
  2. 方式二,就不要使用了,要动用两次动态内存分配
// 方式一
auto sp1 = boost::make_shared<std::string>("make_shared");

// 方式二
boost::shared_ptr<std::string> sp2(new std::string("make_shared"));

2.3 shared_ptr中的指针转型问题

对于shared_ptr专门提供了三个指针转型,为什么么?
一个很简单的想法:shared_ptr指针转型后还应该是shared_ptr
但是标准库中四种转型,结果就不是shared_ptr了
嘿嘿,总有人会绕后门,绝不可写出下面这种危险的代码,除非你清楚知道你在干啥!!!

static_cast<T*>(p.get());
  1. static_pointer_cast<T> -------------- 向上转,派生类到基类
  2. dynamic_pointer_cast<T>----------- 向下转,基类到派生类
  3. const_pointer_cast<T> --------------去除const属性

这种代码一般在多态的时候才会遇到,就第一、第二简单的举例

boost::shared_ptr<base> sp(new derived);
auto sp1 = dynamic_pointer_cast<derived>(sp);	// 下转
auto sp2 = static_pointer_cast<base>(sp1);		// 上转

2.4 shared_ptr在容器中的应用

shared_ptr搭配容器有两种典型的方式

1. shared_ptr<容器> <------> shared_ptr<vector<int>>
2. 容器<shared_ptr> <------> vector<shared_ptr<int>>

这两种方式还是很好理解的,下面举一个简单的例子
eg: 一个容器里面装的是智能指针,而智能指针管理的是int

	typedef std::vector<boost::shared_ptr<int>> vs;
	vs v(10);
	// pos是容器的迭代器指针,正如下面需要两次解引用
	for (auto pos = v.begin(); pos != v.end(); ++pos)
	{
	    (*pos) = boost::make_shared<int>(++i); // 工厂函数进行赋值,更加高效
	    std::cout << **pos << ", ";            // 第一次获取数组中地址,第二次获取值
	}
	
    // Method2 我一般用这种,,,
    for (auto &e : v)
    {
        e = boost::make_shared<int>(++i);
        std::cout << *e << ", ";
    }

2.5 定制删除器D

对于一般的简单数据类型,类我们使用默认的删除器就好了, 当然如果我们处理的是资源类,使用自定义的删除器会发挥shared_ptr的真实作用,是真正意义上的智能

常见的资源有

  1. 数据库、文件句柄、socket、自定义资源类等等

下面举两个例子

2.5.1 shared_ptr管理FILE文件指针

将打开的文件句柄交给shared_ptr管理,如果发生任何不当行为,shared_ptr会帮助我们自动释放资源,程序员也不用自己显式写出fclose。
如果文件不存在,那么shared_ptr管理的是空指针,这样会在fclose发生异常,为了避免,先打开后判断是否为空哈

boost::shared_ptr<FILE> fp(fopen("/home/topeet/myBoost/chap3_ptr/text.txt", "r"), fclose);

2.5.2 shared_ptr管理socket

一个自定义类管理套接字资源,自定义一个合适的资源释放函数,更加高效!!!

	class socket_t { };
	socket_t *open_socket() {
	    std::cout << "open_socket" << std::endl;
	    return new socket_t;
	}
	void close_socket(socket_t * s) {		// 资源释放函数
	    std::cout << "close_socket" << std::endl;
	    delete s;
	}
	
	socket_t *s = open_socket();
	if (s != nullptr)						// 其实这里可以不判断,delete 空指针不会异常,但是这不符合代码严谨性
	    boost::shared_ptr<socket_t> p(s, close_socket);
	else
	    return -1;

2.5.3 shared_ptr 高级用法

注意,我们假用一个空指针,在其退出作用域时,自动调用任意的函数!!!

void any_fun(void* p) {....}
boost::shared_ptr<void> p(nullptr, any_func); //在其退出作用域时,自动调用任意的函数!!!

2.6 关于桥接方式–减少文件的依赖关系,减少编译时间

关于这个主题,很复杂,我写了一篇博客
C++ 如何缩短编译时间(Effective C++ 条款31:将文件间的编译依存关系降至最低)
https://blog.csdn.net/weixin_39956356/article/details/110404940
有两种技术

  1. 技术1:运用Handle Class技术降低接口和实现的耦合性
  2. 技术2:运用Interface Class技术降低接口和实现的耦合性

对于技术1,shared_ptr主要用在private中—提供接口类中
对于技术2,shared_ptr主要用在create上,很有意思
具体细节,请浏览上面的链接

2.7 借shared_ptr我们分享下别名构造函数(alias constructor)

之后待续…

相关标签: Boost c++