(2条消息) 为什么多线程读写 shared_ptr 要加锁?_陈硕的Blog-CSDN博客
(2条消息) C++11使用make_shared的优势和劣势_yagerfgcs的博客-CSDN博客_makeshared
(2条消息) C++11新特性之十:enable_shared_from_this_草上爬的博客-CSDN博客
第22课 weak_ptr弱引用智能指针 - 浅墨浓香 - 博客园 (cnblogs.com)
(2条消息) C++:智能指针(5)——enable_shared_from_this工作原理、源码分析_cocoa0409的博客-CSDN博客
(2条消息) C++11新特性之十:enable_shared_from_this_草上爬的博客-CSDN博客
? 用来自动的管理 指针的一个类
以前没有shared_ptr的时候需要,手动 delete,这样带来三个明显问题:
可能存在忘记delete,造成内存泄漏
可能存在重复delete,造成重复释放
在new 和 手动delete中间,可能存在异常抛出,这样也会是内存泄漏的原因
有个shared_ptr,就可以在对象当前作用域结束后,自动 释放 内部管理的空间(RAII技术)
当只有一个shared_ptr指向这片内存,在他析构的时候就会自动把这片内存释放
头文件
#include <memory>
常用使用方式
#include <iostream>
#include <memory>
using namespace std;
class Test{
public:
Test() {
std::cout << "Test()" << std::endl;
}
~Test() {
std::cout << "~Test()" << std::endl;
}
};
int main()
{
shared_ptr<Test> test(new Test());
return 0;
}
输出:
Test()
~Test()
在main的这个{}作用域内结束后,自动释放Test空间
必须用explicit构建,因为内部构造函数使用explicit的方式
都是使用智能指针类值的方式管理 其他内存,一般不会使用智能指针还是 new出来的这种方式
使用的方式就和指针一样
test->xx函数
等同于Test的指针->xx函数
可以使用 *test,得到原始指针的引用值
test.get()得到内部裸漏的指针,多用于兼容其他 c版本的函数
默认的可以直接 shared_ptr
shared_ptr<Test> test;
test.reset(new Test());
先把对象创建之后再把管理的对象 移入
reset函数
将当前的指针和计数值清0
? 如何管理的内存呢?看一下shared_ptr内部
?
? 1. 当use_count 为1的时候的析构被调用 就会析构 _M_ptr
? 否则use_count减一
那么__shared_count这个指针什么时候析构呢?
当weak_count为1的时候的析构被调用 就会析构 _M_ptr
否则weak_count–
#include <iostream>
#include <memory>
using namespace std;
class Test{
public:
Test() {
std::cout << "Test()" << std::endl;
}
~Test() {
std::cout << "~Test()" << std::endl;
}
};
class Children;
class Father{
public:
~Father() {
std::cout << "~Father()" << std::endl;
}
shared_ptr<Children> m_ptr;
};
class Children{
public:
~Children() {
std::cout << "~Children()" << std::endl;
}
shared_ptr<Father> m_ptr;
};
int main()
{
shared_ptr<Father> A(new Father());
shared_ptr<Children> B(new Children());
A->m_ptr = B;
B->m_ptr = A;
return 0;
}
互相的m_ptr保存了对方的值,那么结果就是析构函数不会被调用,造成内存泄漏
解决办法 weak_ptr,主要作用就是 可以使用lock产生shared_ptr,但是不对内存空间进行管理(shared_ptr指针的作用是管理),weak_ptr仅仅是使用
shared_ptr强引用:管理内存
weak_ptr弱引用:仅仅计数
#include <iostream>
#include <memory>
using namespace std;
class Test{
public:
Test() {
std::cout << "Test()" << std::endl;
}
~Test() {
std::cout << "~Test()" << std::endl;
}
};
class Children;
class Father{
public:
~Father() {
std::cout << "~Father()" << std::endl;
}
shared_ptr<Children> m_ptr;
};
class Children{
public:
~Children() {
std::cout << "~Children()" << std::endl;
}
weak_ptr<Father> m_ptr;
};
int main()
{
shared_ptr<Father> A(new Father());
shared_ptr<Children> B(new Children());
A->m_ptr = B;
B->m_ptr = A;
return 0;
}
? 其实他和shared_ptr差别不大
? 主要有两点:
通过shared_ptr来给weak_ptr构造
析构时的不一样
void
_M_weak_release() noexcept
{
// Be race-detector-friendly. For more info see bits/c++config.
_GLIBCXX_SYNCHRONIZATION_HAPPENS_BEFORE(&_M_weak_count);
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_weak_count, -1) == 1)
{
_GLIBCXX_SYNCHRONIZATION_HAPPENS_AFTER(&_M_weak_count);
if (_Mutex_base<_Lp>::_S_need_barriers)
{
// See _M_release(),
// destroy() must observe results of dispose()
_GLIBCXX_READ_MEM_BARRIER;
_GLIBCXX_WRITE_MEM_BARRIER;
}
_M_destroy();
}
}
也就是说 weak_ptr会增加weak_count的值
本质里面说过,__shared_count这个指针会在weak_count为1析构时候释放,那么一旦 使用中出现了 weak_ptr,weak_ptr的存在就影响并管理了这个指针
weak_ptr的出现更像是辅助shared_ptr
wpCStateMachine
wpVR_ActionHandle
wpVR_RequestHandle
static std::weak_ptr<VR_SDSIF> s_SDSIF;
void SetStateManager(std::weak_ptr<VR_SDSStateManager> wpStateManager) {m_wpStateManager = wpStateManager;}
std::weak_ptr<VR_SDSStateManager> m_wpStateManager;
typedef std::weak_ptr
typedef std::weak_ptr
? 我们知道 weak_ptr的作用,但是什么时候去使用呢?
? weak_ptr去使用管理的内存的使用应该lock产生shared_ptr
就是解决循环引用
观察者模式
1. 观察者模式是在subject状态发生改变时,通知观察者的一种设计模式。
2. 在多数实现中,每个subject持有指向观察者的指针,这使得当subject状态改变时可以很容易通知观察者。
3. subject不会控制其观察者的生存期,因此应该是持有观察者的weak_ptr指针。同时在subject的使用某个指针时,可以先确定是否空悬。
1. 考虑一个工厂函数loadWidget,该函数基于唯一ID来创建一些指向只读对象的智能指针。
2. 假设该只读对象需要被频繁使用,而且经常需要从文件或数据库中加载。那么可以考虑将对象缓存起来。同时为了避免过量缓存,当不再使用时,则将该对象删除。
3. 由于带缓存,工厂函数返回unique_ptr类型显然不合适。因为调用者和缓存管理器均需要一个指向这些对象的指针。
4. 当用户用完工厂函数返回的对象后,该对象会被析构,此时相应的缓存条目将会空悬。因为可以考虑将工厂函数的返回值设定为shared_ptr类型,而缓存类型为weak_ptr类型。
enable_shared_from_this
参见(2条消息) C++11新特性之十:enable_shared_from_this_草上爬的博客-CSDN博客
用继承enable_shared_from_this的方式来将this指针传递给其他地方
注意this指针一定要先被shared_ptr管理,才能gp1->getptr()调用,否则就是空值
借用 http://bitdewy.github.io/blog/2014/01/12/why-make-shared/的图
那么 使用make_shared的好处有哪些
f(shared_ptr<Test>(new Test()), getMemory());
? 我们以为的顺序:
1. new Test
2. std::shared_ptr
3. getMemory()
不同的语言对于函数参数的顺序是不确定的, 但是 new Test肯定在 std::shared_ptr之前
那么
当getMemory发生异常,内存泄漏就出来了
解决办法:
独立的语句
shared_ptr
f(a, getMemory());
make_shared
f(make_shared
make_shared的坏处
可能造成部分内存锁定
因为上面的分析我们知道 weak_count管理哪些引用的个数,当这个为0释放那个count结构
但是make_shared 将管理的对象内存和count结构的内存绑定在一起,尽管use_count计数为0释放了空间
由于count结构可能存在,只有当 weak_count释放count结构的时候,这整个的由make_shared 释放的空间才能归还
题外
Foo 的构造函数参数要传给 make_shared(),后者再传给 Foo::Foo(),这只有在 C++11 里通过 perfect forwarding 才能完美解决
? shared_ptr对管理的内存是线程安全的
? 因为源码中
_Atomic_word _M_use_count; // #shared
_Atomic_word _M_weak_count;
析构的时候
if (__gnu_cxx::__exchange_and_add_dispatch(&_M_use_count, -1) == 1)
{
? 类型都是原子类型,对他们的操作都是采用原子操作来实现了
? 即使多个线程来减少,他仅仅判断_M_use_count为1的时候进行 -1才会释放内存
? 多线程引用计数是安全的,但是对象的读写不是
? 如果要从多个线程读写同一个 shared_ptr 对象,那么需要加锁
? 参见(2条消息) 为什么多线程读写 shared_ptr 要加锁?_陈硕的Blog-CSDN博客
? 本质就是 在 内部的指针和 count计数对象赋值这两个步骤整体不是原子的问题
默认的指针隐式转换 到了 模板中该怎么办?
比如 子类指针可以直接赋值给 父类指针
子类对象可以直接赋值给 父类对象
class Top {
//.....
};
class Middle : public Top {
//...
};
class Bottom : public Top {
//...
};
int main(int argc, char**argv) {
Top* middle = new Middle(); //子类赋值给父类
Top* bottom = new Bottom(); //子类赋值给父类
const Top* top = middle; //父类隐式给const父类
return 0;
}
上面肯定没问题,那么当和智能指针碰撞呢,我们希望有这样的实现
Shared_ptr<Top> pt1 = Shared_ptr<Middle>(new Middle());
Shared_ptr<Top> pt2 = Shared_ptr<Middle>(new Bottom());
但是!!!,如果不自己处理,默认是不可能的的!!,因为 Shared_ptr
解决办法:
? 编写转换构造函数,这个转换构造函数的参数肯定不能是某一个固定类型的参数,因为 Top会有很多子类,一个一个写累死了
template<typename T>
class SmartPtr {
public:
template<typename U>
SmartPtr(const SmartPtr<U>& user)
: m_ptr(user.m_ptr)
{
}
T* get() const {
return m_ptr;
}
private:
T* m_ptr;
};
? 变成上面这样就解决了
不加 explicit的原因是 原生的就支持 隐式转换
并且 m_ptr(user.m_ptr),编译器会判断这两个内部指针能否进行赋值,这也就是实现了 相关的类赋值以及 子类赋值给父类的强制要求
只有在 U指针可以隐式转换为 T指针才能被编译
另一个成员函数模板的用处: 赋值操作
比如标准 shared_ptr之类的
template<class T>
class shared_ptr {
public:
template<class Y>
explicit shared_ptr(Y *p); //构造,可以进行任何和T兼容的指针的赋值
template<class Y>
shared_ptr(shared_ptr<Y>const &p); //对shared_ptr进行隐式拷贝构造
template<class Y>
explicit shared_ptr(weak_ptr<Y>const &p); //对weak_ptr进行隐式拷贝构造
template<class Y>
explicit shared_ptr(auto_ptr<Y>const &p); //对auto_ptr进行隐式拷贝构造
template<class Y>
shared_ptr& operator=(shared_ptr<Y>const &p); //对shared_ptr进行赋值拷贝
template<class Y>
shared_ptr& operator=(auto_ptr<Y>const &p); //对auto_ptr进行隐式拷贝构造
};
当泛化构造和 默认的拷贝构造 ?
member template并不改变语言规则--》如果你没有主动实现拷贝构造,编译器默认给你生成
这二者不冲突,如果你想要控制copy构造的每一个细节,必须同时声明实现泛化和拷贝构造
? 基于问题3,为了精准调用对应的析构函数,即使没有虚析构的存在下
? 上面例子中
Shared_ptr<Top> pt1 = Shared_ptr<Middle>(new Middle());
? pt1在析构的时候调用的是 delete Middle, 而不是 Top
? 看源码:
template<typename _Tp1>
shared_ptr(const shared_ptr<_Tp1>& __r, _Tp* __p) noexcept
: __shared_ptr<_Tp>(__r, __p) { }
然后 __shared_ptr:
template<typename _Tp1, typename = typename
std::enable_if<std::is_convertible<_Tp1*, _Tp*>::value>::type>
__shared_ptr(const __shared_ptr<_Tp1, _Lp>& __r) noexcept
: _M_ptr(__r._M_ptr), _M_refcount(__r._M_refcount)
{ }
_Tp1是参数类型推导,类的类型是 _Tp
调用->的使用使用的类型是 _ Tp(top)类型,在 __shared_ptr参数转换构造的时候,_M_ptr参数内部包含的是 _Tp1(Middle)类型
这样之后不管你 shared_ptr指向什么类型,即使是void, 那么析构的时候 Middle仍然被正常析构
当出现虚析构的时候 shared_ptr内部的指针和 __shared_count内部的指针值也可能不同
shared_ptr的一切(本质、make_shared、weak_ptr)
原文:https://www.cnblogs.com/zero-waring/p/14802112.html