shared_ptr描述
声明
shared_ptr属于C++11特性中新加的一种智能指针,它的实现方式是模版类,头文件<memory>
template <class T> class shared_ptr
所以使用shared_ptr的声明方式即为
std::shared_ptr<type_id> statement
其中type_id为类型(可以是基本数据类型或者类),statement即为模版类对象
作用
shared_ptr 的理解如下:
- 使用一种叫做RAII(Resource Acquisition Is Initialization)的技术作为实现基础:
在对象构造时获取资源,接着控制对资源的访问使之在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源
raii技术的好处是:- 不需要显式释放资源
- 对象所拥有的资源在其生命周期内始终有效
- 防止忘记调用delete释放内存或者程序异常退出时没有释放内存。
- 同时它能够将值语义转为引用语义(即shared_ptr可以让多个指针指向相同的对象,共享同一块地址空间),shared_ptr使用引用技术方式来统计当前对象被引用的次数,每一次执行析构函数,引用计数就会-1,当引用计数减为0时自动删除所指对象,回收对象空间。
原理实现
常用操作以及源码实现如下:
类声明如下
temple<typename T>
class SharedPtr {
public:
...
private:
T *_ptr;
int *_refCount; //这里使用int型指针是为了保证拷贝构造时同一个地址空间的引用计数增加
};
constructor
构造函数,初始化的时候默认引用计数为0
在使用普通指针初始化两个shared_ptr的时候,两个shared_ptr的引用计数都为1SharedPtr() : _ptr((T *)0), _refCount(0) {}
在进行拷贝构造的时候,使用shared_ptr去初始化另一个shared_ptr的时候引用计数会+1SharedPtr(T *obj) : _ptr(obj), _refCount(new int(1)) { }
SharedPtr(SharedPtr &other) : _ptr(other._ptr), _refCount(&(++*other._refCount)) { }
destructor
析构函数,析构的时候在指针不为空且引用计数为0的时候释放空间~SharedPtr() { if (_ptr && --*_refCount == 0) { delete _ptr; delete _refCount; } }
operator =
当使用一个shared_ptr给另一个shared_ptr赋值的时候这里需要注意- 由于指针指向发生变化,原来的_ptr指针的引用计数要–,且当达到了0的时候要注意回收原来指针的空间
- _ptr又指向了新的_ptr,则新的_ptr指针的引用计数要++
SharedPtr &operator=(SharedPtr &other) { if(this==&other) return *this; //新指针引用计数要++ ++*other._refCount; //原指针引用计数要--,如果为0,则释放空间 if (--*_refCount == 0) { delete _ptr; delete _refCount; } //重新进行指向 _ptr = other._ptr; _refCount = other._refCount; return *this; }
operator*
解引用运算符,直接返回底层指针的引用,即共享的地址空间内容T &operator*() { if (_refCount == 0) return (T*)0; return *_ptr; }
operator ->
指针运算符T *operator->() { if(_refCount == 0) return 0; return _ptr; }
函数使用
主要案例如下
- 构造函数
constructor
,std::shared_ptr初始化案例如下,以及对应的refcount打印
输出如下#include <iostream> #include <memory> struct C {int* data;}; int main () { std::shared_ptr<int> p1;//默认构造函数,refcount为0 std::shared_ptr<int> p2 (nullptr);//使用一个空的对象初始化时refcount也为0 //普通指针初始化是引用计数为1,p3,p4 std::shared_ptr<int> p3 (new int); std::shared_ptr<int> p4 (new int, std::default_delete<int>()); //拥有allocator的时候初始化同样引用计数为1 //但是紧接着又用改智能指针拷贝构造初始化其他智能指针p6, //所以最后其引用计数为2,p5 std::shared_ptr<int> p5 (new int, [](int* p){delete p;}, std::allocator<int>()); //这里p6本身为1,但是因为使用std::move去初始化p7,将p6指向转给了p7 //则p6智能指针recount--变为0,p7 ++由1变为2 std::shared_ptr<int> p6 (p5); std::shared_ptr<int> p7 (std::move(p6)); std::shared_ptr<int> p8 (std::unique_ptr<int>(new int)); std::shared_ptr<C> obj (new C); std::shared_ptr<int> p9 (obj, obj->data); std::cout << "use_count:\n"; std::cout << "p1: " << p1.use_count() << '\n'; std::cout << "p2: " << p2.use_count() << '\n'; std::cout << "p3: " << p3.use_count() << '\n'; std::cout << "p4: " << p4.use_count() << '\n'; std::cout << "p5: " << p5.use_count() << '\n'; std::cout << "p6: " << p6.use_count() << '\n'; std::cout << "p7: " << p7.use_count() << '\n'; std::cout << "p8: " << p8.use_count() << '\n'; std::cout << "p9: " << p9.use_count() << '\n'; return 0; }
use_count: p1: 0 p2: 0 p3: 1 p4: 1 p5: 2 p6: 0 p7: 2 p8: 1 p9: 2
- 析构函数
输出如下// shared_ptr destructor example #include <iostream> #include <memory> int main () { auto deleter = [](int*p){ std::cout << "[deleter called]\n"; delete p; }; //使用特殊的delete函数去构造,析构的时候会执行改delete 中lamada表达式内容.即构造函数案例中的p5初始化方式 std::shared_ptr<int> foo (new int,deleter); std::cout << "use_count: " << foo.use_count() << '\n'; return 0; // [deleter called] }
use_count: 1 [deleter called]
- =赋值运算符
输出如下// shared_ptr::operator= example #include <iostream> #include <memory> int main () { std::shared_ptr<int> foo; std::shared_ptr<int> bar (new int(10)); /* 此时foo的引用计数为0,bar初始化后引用计数为1 这里进行赋值操作,即foo的指向发生了变化,指向了bar 1.foo引用计数--,因为已经为0了,此时直接释放foo原来的空间 2.bar引用计数++变为2 3.更改foo的引用计数和bar引用计数相等,并使得foo指向bar.因为他们共享同一个空间 执行完之后fool和bar引用计数都相等,且解引用后数值都为0 */ foo = bar; // copy std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n'; std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n'; /* 这里重新对bar进行了初始化,即原先的指向发生了更改,所以它的引用计数--,并且内容变为新的地址空间内容20 foo继续指向原先空间,但是内容并未变化。同时原先地址因为bar并不引用了,所以foo的引用计数-- */ bar = std::make_shared<int> (20); // move std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n'; std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n'; std::unique_ptr<int> unique (new int(30)); foo = std::move(unique); // move from unique_ptr std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n'; std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n'; return 0; }
*foo: 10 foo.count 2 *bar: 10 bar.count 2 *foo: 10 foo.count 1 *bar: 20 bar.count 1 *foo: 30 foo.count 1 *bar: 20 bar.count 1
- shared_ptr::swap,交换两个shared_ptr地址空间内容,但并不破坏各自引用计数
输出如下// shared_ptr::swap example #include <iostream> #include <memory> int main () { std::shared_ptr<int> foo (new int(10)); std::shared_ptr<int> bar (new int(20)); std::cout << "befor swap" << '\n'; std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n'; std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n'; foo.swap(bar); std::cout << "after swap" << '\n'; std::cout << "*foo: " << *foo << " foo.count " << foo.use_count() << '\n'; std::cout << "*bar: " << *bar << " bar.count " << bar.use_count() << '\n'; return 0; }
befor swap *foo: 10 foo.count 1 *bar: 20 bar.count 1 after swap *foo: 20 foo.count 1 *bar: 10 bar.count 1
- shared_ptr::reset 替换所管理的对象
输出如下,可以看到reset之后的地址发生了变化,即更改了指针的指向// shared_ptr::reset example #include <iostream> #include <memory> int main () { std::shared_ptr<int> sp; // empty sp.reset (new int); // 替换所管理对象,让其更换地址指向 std::cout << *sp << " " << sp.use_count() << " " << sp << '\n'; *sp=10; std::cout << *sp << " " << sp.use_count() << " " << sp << '\n'; sp.reset (new int); // 清除上一个指针指向的内容,重新进行更换 std::cout << *sp << " " << sp.use_count() << " " << sp << '\n'; *sp=20; std::cout << *sp << " " << sp.use_count() << " " << sp << '\n'; sp.reset(); // deletes managed object //std::cout << *sp << " " << sp.use_count() << '\n'; return 0; }
0 1 0x434cd50 10 1 0x434cd50 0 1 0x434cd90 20 1 0x434cd90
- shared_ptr::get获取初始指针
输出如下:// shared_ptr::get example #include <iostream> #include <memory> int main () { int* p = new int (10); std::shared_ptr<int> a (p); std::shared_ptr<int> b (new int(20)); //此时a和p共享同一个地址空间,所以a和p的内容都为0,地址空间一样 if (a.get()==p) std::cout << "a and p point to the same location " << a << " " << p << '\n'; std::cout << *a.get() << "\n"; std::cout << *a << "\n"; std::cout << *p << "\n"; //此时a将共享空间释放,重新更换指向b,但是*p为普通指针,并无法跟随a更换指,所以p的地址内容变为0 a=b; std::cout << "a and p after copy " << a << " " << b << '\n'; // three ways of accessing the same address: std::cout << *a.get() << "\n"; std::cout << *a << "\n"; std::cout << *p << "\n"; return 0; }
a and p point to the same location 0x21cf2f0 0x21cf2f0 10 10 10 a and p after copy 0x21cf330 0x21cf330 20 20 0
关于shared_ptr循环引用问题
循环引用是指两个shared_ptr初始化之后相互指向,在函数作用域结束之后由于两个指针都保持相互的指向,引用计数都为1,此时各自占用的内存空间无法释放,最终产生内存泄露
举例如下:
#include<iostream>
#include<memory>
using namespace std;
class B;
class A{
public:
shared_ptr<B> ptr_A;
~A(){
cout << "refcount " << ptr_A.use_count() << '\n';
cout<<"~A()"<<endl;
}
};
class B{
public:
//shared_ptr<A> ptr_B;//当采用shared_ptr指向A时会形成循环引用,则什么都不会输出说明对象没有被析构,可怕的内存泄露....
shared_ptr<A> ptr_B;//当采用弱引用时,避免了循环引用,有输出,说明对象被析构了
~B(){
cout << "refcount " << ptr_B.use_count() << '\n';
cout<<"~B()"<<endl;
}
};
int main(){
shared_ptr<A> a(new A);
shared_ptr<B> b(new B);
a->ptr_A=b;
b->ptr_B=a;//若是循环引用:当a、b退出作用域的时候,A对象计数不为1(b保留了个计数呢),同理B的计数也不为1,那么对象将不会被销毁,内存泄露了...
cout << a.use_count() << " " << b.use_count()<< endl;
return 0;
}
输出如下,可以看到释放的之前两个智能指针的引用计数都为2,析构的时候各自引用计数执行–到·1,最终无法释放
2 2
将classB中的shared_ptr更改为weak_ptr即可成功释放
#include<iostream>
#include<memory>
using namespace std;
class B;
class A{
public:
shared_ptr<B> ptr_A;
~A(){
cout << "refcount " << ptr_A.use_count() << '\n';
cout<<"~A()"<<endl;
}
};
class B{
public:
//shared_ptr<A> ptr_B;//当采用shared_ptr指向A时会形成循环引用,则什么都不会输出说明对象没有被析构,可怕的内存泄露....
weak_ptr<A> ptr_B;//当采用弱引用时,避免了循环引用,有输出,说明对象被析构了
~B(){
cout << "refcount " << ptr_B.use_count() << '\n';
cout<<"~B()"<<endl;
}
};
int main(){
shared_ptr<A> a(new A);
shared_ptr<B> b(new B);
a->ptr_A=b;
b->ptr_B=a;//若是循环引用:当a、b退出作用域的时候,A对象计数不为1(b保留了个计数呢),同理B的计数也不为1,那么对象将不会被销毁,内存泄露了...
return 0;
}
输出如下,调用析构函数之前引一个智能指针的引用计已经将为1,执行析构之后即为0
1 2
refcount 1
~A()
refcount 0
~B()
参考文档:
http://www.cplusplus.com/reference/memory/shared_ptr/
https://www.xuebuyuan.com/3190713.html