C++ shared_ptr用法、简析、案例
shared_ptr用法
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. 最重要的是,应用案例,也是本文的核心。
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 &加上,问一下两个问题
- 我需要拷贝吗?需要就去掉&
- 我需要修改值吗?需要去掉const
2.1 reset()的理解
reset()也是一族函数,本意是即将管理另外一个指针p,放弃当前的。
如果p为空,管理另外一个指针p(管理空指针),放弃当前的。相当于置0。
注意:
- p只是原始指针,并不是智能指针!!!
- 从原理上讲,放弃原来的—计数变量会减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;
}
输出:
- sp管理”make_shared123“,引用计数加1
- 之后sp管理新的”xxxx“,相当于原来的已经释放了,计数器先减1,后增加1,计数器值保持不变
2.2 make_shared的理解
通常make_shared()函数比直接创建shared_ptr对象要高效,且只分配一次内存
- 方式一,直接使用make_shared效率更高,不仅快而且高效,分配一次内存
- 方式二,就不要使用了,要动用两次动态内存分配
// 方式一
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());
- static_pointer_cast<T> -------------- 向上转,派生类到基类
- dynamic_pointer_cast<T>----------- 向下转,基类到派生类
- 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的真实作用,是真正意义上的智能
常见的资源有
- 数据库、文件句柄、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:运用Handle Class技术降低接口和实现的耦合性
- 技术2:运用Interface Class技术降低接口和实现的耦合性
对于技术1,shared_ptr主要用在private中—提供接口类中
对于技术2,shared_ptr主要用在create上,很有意思
具体细节,请浏览上面的链接
2.7 借shared_ptr我们分享下别名构造函数(alias constructor)
之后待续…
下一篇: 字节对齐、8字节对齐