1、原理:
shared_ptr允许拷贝和赋值,其底层实现是以"引用计数"为基础的,通过引用计数来控制空间的释放,每一个shared_ptr的拷贝都指向相同的内存。当有新的指针指向这块空间时,内部的引用计数加1,每析构一次,内部的引用计数减1,直到引用计数减为0时自动删除所指向的堆内存。shared_ptr内部的引用计数是线程安全的,但是对象的读取需要加锁。
-
初始化。智能指针是个模板类,可以指定类型,传入指针通过构造函数初始化,也可以使用make_shared函数初始化。不能将指针直接赋值给一个智能指针(一个是类,一个是指针),例如std::shared_ptr<int> sp = new int(1)的写法是错误的,应该用std::shared_ptr<int> sp(new int(1))或std::shared_ptr<int> sp = std::make_shared<int>(1),sp.reset(new int(2)),std::make_shared里面调用了new操作符分配内存。使用时记得包含头文件#include<memory>。
-
注意:不要用一个原始指针初始化多个shared_ptr,否则会造成二次释放同一内存。
-
注意:避免循环引用,shared_ptr的一个最大陷阱是循环引用,循环引用会导致堆内存无法正确释放,导致内存泄漏。
-
注意:shared_ptr尽管使用计数提供了原子性修改操作, 但
shared_ptr
的赋值操作由复制对象指针和修改使用计数两个操作复合而成,因此仍不是线程安全的。shared_ptr 是否线程安全?
2、简单实现:
#include<iostream>
#include<assert.h>
using namespace std;
template<typename T>
class SharedPtr
{
public:
SharedPtr(T* ptr = nullptr);
SharedPtr(const SharedPtr<T>& sp);
SharedPtr<T>& operator=(SharedPtr<T>& sp);
T& operator*();
T* operator->();//?怎么用
~SharedPtr();
int count()
{
return *_pCount;
}
private:
T* _ptr;
size_t* _pCount;
};
template<typename T>
SharedPtr<T>::SharedPtr(T* ptr) :_ptr(ptr)
{
if (_ptr)
{
_pCount = new size_t(1);
cout << "通过普通指针初始化" << endl;
}
else
{
_pCount = new size_t(0);
cout << "初始化为空指针" << endl;
}
}
template<typename T>
SharedPtr<T>::SharedPtr(const SharedPtr<T>& sp)
{
if (this != &sp)
{
_ptr = sp._ptr;
_pCount = sp._pCount;
++(*_pCount);
cout << "通过智能指针初始化" << endl;
}
}
template<typename T>
SharedPtr<T>& SharedPtr<T>::operator=(SharedPtr<T>& sp)
{
if (_ptr == sp._ptr)
return *this;
if (_ptr)
{
*_pCount--;
if (*_pCount == 0)
{
delete _ptr;
delete _pCount;
}
}
_ptr = sp._ptr;
_pCount = sp._pCount;
++*_pCount;
cout << "调用重载赋值运算符" << endl;
return *this;
}
template<typename T>
T& SharedPtr<T>::operator*()
{
assert(_ptr != nullptr);
cout << "调用重载*号运算符" << endl;
return *_ptr;
}
template<typename T>
T* SharedPtr<T>::operator->()
{
assert(_ptr != nullptr);
cout << "调用重载*号运算符" << endl;
return _ptr;
}
template<typename T>
SharedPtr<T>::~SharedPtr()
{
cout << "调用析构函数" << endl;
if (--(*_pCount) == 0)
{
delete _ptr;
delete _pCount;
_ptr = nullptr;
_pCount = nullptr;
}
}
//测试
void main()
{
{
int *p = new int(1);
SharedPtr<int> sp1;
cout << "sp1:" << sp1.count() << endl;
SharedPtr<int> sp2(p);
cout << "sp2:" << sp2.count() << endl;
SharedPtr<int> sp3(sp2);
cout << "sp2:" << sp2.count() << endl;
cout << "sp3:" << sp3.count() << endl;
sp1 = sp3;
cout << *sp1 << endl;
cout << "sp1:" << sp1.count() << endl;
getchar();
}
getchar();
}
结果:
3、智能指针优缺点:
> 优点
- 如果没有清晰、逻辑条理的所有权安排, 不可能管理好动态分配的内存.
- 传递对象的所有权, 开销比复制来得小, 如果可以复制的话.
- 传递所有权也比”借用”指针或引用来得简单, 毕竟它大大省去了两个用户一起协调对象生命周期的工作.
- 如果所有权逻辑条理, 有文档且不紊乱的话, 可读性会有很大提升.
- 可以不用手动完成所有权的登记工作, 大大简化了代码, 也免去了一大波错误之恼.
- 对于 const 对象来说, 智能指针简单易用, 也比深度复制高效.
> 缺点
- 不得不用指针(不管是智能的还是原生的)来表示和传递所有权. 指针语义可要比值语义复杂得许多了, 特别是在 API 里:这时不光要操心所有权, 还要顾及别名, 生命周期, 可变性以及其它大大小小的问题.
- 其实值语义的开销经常被高估, 所以所有权传递带来的性能提升不一定能弥补可读性和复杂度的损失.
- 如果 API 依赖所有权的传递, 就会害得客户端不得不用单一的内存管理模型.
- 如果使用智能指针, 那么资源释放发生的位置就会变得不那么明显.
std::unique_ptr
的所有权传递原理是 C++11 的 move 语法, 后者毕竟是刚刚推出的, 容易迷惑程序员.- 如果原本的所有权设计已经够完善了, 那么若要引入所有权共享机制, 可能不得不重构整个系统.
- 所有权共享机制的登记工作在运行时进行, 开销可能相当大.
- 某些极端情况下 (例如循环引用), 所有权被共享的对象永远不会被销毁.
- 智能指针并不能够完全代替原生指针.
4、shared_ptr相互引用问题
例子:
#include <memory>
#include <iostream>
class TestB;
class TestA
{
public:
TestA()
{
std::cout << "TestA()" << std::endl;
}
void ReferTestB(std::shared_ptr<TestB> test_ptr)
{
m_TestB_Ptr = test_ptr;
}
~TestA()
{
std::cout << "~TestA()" << std::endl;
}
private:
std::shared_ptr<TestB> m_TestB_Ptr; //TestB的智能指针
};
class TestB
{
public:
TestB()
{
std::cout << "TestB()" << std::endl;
}
void ReferTestB(std::shared_ptr<TestA> test_ptr)
{
m_TestA_Ptr = test_ptr;
}
~TestB()
{
std::cout << "~TestB()" << std::endl;
}
std::shared_ptr<TestA> m_TestA_Ptr; //TestA的智能指针
};
int main()
{
std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();
std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();
ptr_a->ReferTestB(ptr_b);
ptr_b->ReferTestB(ptr_a);
return 0;
}
结果:
TestA()
TestB()
分析:
TestA和TestB两个对象都没有被析构,原因是:智能指针ptr_a中引用了ptr_b,同样ptr_b中也引用了ptr_a,在main函数退出前,ptr_a和ptr_b的引用计数均为2,退出main函数后,引用计数均变为1,也就是相互引用。相互引用导致的问题就是释放条件的冲突,最终也可能导致内存泄漏。
5、使用weak_ptr解决相互引用问题
weak_ptr被设计为与shared_ptr共同工作,可以从一个shared_ptr或者另一个weak_ptr对象构造,获得资源的观测权。但weak_ptr没有共享资源,它的构造不会引起指针引用计数的增加。同样,在weak_ptr析构时也不会导致引用计数的减少,它只是一个静静地观察者。weak_ptr没有重载operator*和->,这是特意的,因为它不共享指针,不能操作资源,这是它弱的原因。如要操作资源,则必须使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象,从而操作资源。
#include <iostream>
#include <memory>
class TestB;
class TestA
{
public:
TestA()
{
std::cout << "TestA()" << std::endl;
}
void ReferTestB(std::shared_ptr<TestB> test_ptr)
{
m_TestB_Ptr = test_ptr;
}
void TestWork()
{
std::cout << "~TestA::TestWork()" << std::endl;
}
~TestA()
{
std::cout << "~TestA()" << std::endl;
}
private:
std::weak_ptr<TestB> m_TestB_Ptr;
};
class TestB
{
public:
TestB()
{
std::cout << "TestB()" << std::endl;
}
void ReferTestB(std::shared_ptr<TestA> test_ptr)
{
m_TestA_Ptr = test_ptr;
}
void TestWork()
{
std::cout << "~TestB::TestWork()" << std::endl;
}
~TestB()
{
////把std::weak_ptr类型转换成std::shared_ptr类型
std::shared_ptr<TestA> tmp = m_TestA_Ptr.lock();
tmp->TestWork();
std::cout << "2 ref a:" << tmp.use_count() << std::endl;
std::cout << "~TestB()" << std::endl;
}
std::weak_ptr<TestA> m_TestA_Ptr;
};
int main()
{
std::shared_ptr<TestA> ptr_a = std::make_shared<TestA>();
std::shared_ptr<TestB> ptr_b = std::make_shared<TestB>();
ptr_a->ReferTestB(ptr_b);
ptr_b->ReferTestB(ptr_a);
std::cout << "1 ref a:" << ptr_a.use_count() << std::endl;
std::cout << "1 ref b:" << ptr_a.use_count() << std::endl;
return 0;
}
输出:
TestA()
TestB()
1 ref a:1
1 ref b:1
~TestA::TestWork()
2 ref a:2
~TestB()
~TestA()
分析:
所有的对象最后都能正常释放,不会存在上一个例子中的内存没有释放的问题; ptr_a和ptr_b在main函数中退出前,引用计数均为1,也就是说,在TestA和TestB中对std::weak_ptr的相互引用,不会导致计数的增加。在TestB析构函数中,调用std::shared_ptr tmp = m_TestA_Ptr.lock(),把std::weak_ptr类型转换成std::shared_ptr类型,然后对TestA对象进行调用。
std::weak_ptr支持的调用:
weak_ptr<T> w; //空weak_ptr可以指向类型为T的对象
weak_ptr<T> w(shared_ptr sp); //与sp指向相同对象的weak_ptr, T必须能转换为sp指向的类型
w = p; //p可以是shared_ptr或者weak_ptr,赋值后w和p共享对象
w.reset(); //weak_ptr置为空
w.use_count(); //与w共享对象的shared_ptr的计数
w.expired(); //w.use_count()为0则返回true,否则返回false
w.lock(); //w.expired()为true,返回空的shared_ptr;否则返回指向w的shared_ptr
--------------------------------------------
struct A {
int i;
}
struct B: public A {
typedef std::shared_ptr<B> Ptr;
int c;
}
A a;
a.i = 0;
B::Ptr b(new B);
*static_cast<A*>(b.get()) = a;
b->c = 1;
参考: