【智能指针】shared_ptr基本用法和原理(共享指针)
目录
shared_ptr基本用法
头文件
shared_ptr需要头文件#include <memory>
声明方法
class A
{
A()
{ cout << "A----" <<endl;}
}
//way1
A a;
auto sp1 = std::make_shared<int>(5);
auto sp11 = std::make_shared<A>(a);
//way2
int* p1 = new int[3];
std::shared_ptr<int> sp2(p1);
//way3
std::shared_ptr<A> sp3(new A);
//way4
auto sp31 = std::make_shared<A>(a);
std::shared_ptr<A> sp3(sp31);
std::shared_ptr<int> p1(new int(1)); //方式1
std::shared_ptr<int> p2 = p1; //方式2
std::shared_ptr<int> p3;
p3.reset(new int(1)); //方式3 reset,如果原有的shared_ptr不为空,会使原对象的引用计数减1
std::shared_ptr<int> p4 = std::make_shared<int>(2); //方式4
一般来说 std::make_shared
是最推荐的一种写法。
增加计数
被引用则会增加计数
std::shared_ptr<int> ptr2(sp2);//再次被引用则计数+1
在函数内改变计数,超过生命周期后计数会恢复,test函数内的p1析构了。
void test(int* ptr)
{
std::shared_ptr<int> p1(ptr);
int n = p1.use_count();
std::cout << n << std::endl;
}
得到原指针
get()函数返回原指针
int* n3 = sp1.get();
std::cout << *(sp2.get()) << std::endl;
一个例子
#include "stdafx.h"
#include <iostream>
#include <memory>
void fun(std::shared_ptr<int> sp)
{
std::cout << "fun: sp.use_count() == " << sp.use_count() << '\n';
}
void test(int* ptr)
{
std::shared_ptr<int> p1(ptr);
int n = p1.use_count();
std::cout << n << std::endl;
}
int _tmain(int argc, _TCHAR* argv[])
{
auto sp1 = std::make_shared<int>(5);
int* n3 = sp1.get();
std::cout << *sp1 << '\n';;
std::cout << "sp1.use_count() == " << sp1.use_count() << '\n';
std::shared_ptr<int> p2(sp1);
std::cout << "sp1.use_count() == " << sp1.use_count() << '\n';
fun(sp1);
fun(sp1);
std::cout <<"------------------"<< std::endl;
std::cout << std::endl;
int* p1 = new int[3];
memset(p1, 0, sizeof(int) * 3);
*p1 = 11;
*(p1 + 1) = 22;
*(p1 + 2) = 33;
std::shared_ptr<int> sp2(p1);
int n = sp2.use_count();
std::cout << n << std::endl;
std::shared_ptr<int> ptr2(sp2);
n = ptr2.use_count();
std::cout << n << std::endl;
std::shared_ptr<int> ptr3 = sp2;
n = ptr3.use_count();
std::cout << n << std::endl;
//std::cout << sp2 << std::endl;
std::cout << *(sp2.get()) << std::endl;
std::cout << *(sp2.get()+1) << std::endl;
std::cout << *(sp2.get() + 2) << std::endl;
return 0;
}
https://blog.csdn.net/Richelieu_/article/details/83548000
shared_ptr
std::shared_ptr采用引用计数,每一个shared_ptr的拷贝都指向相同的内容,当最后一个shared_ptr析构的时候,内存被释放
初始化shared_ptr对象
#include<iostream>
#include<memory>
int main(){
std::shared_ptr<int> p1(new int(1)); //方式1
std::shared_ptr<int> p2 = p1; //方式2
std::shared_ptr<int> p3;
p3.reset(new int(1)); //方式3 reset,如果原有的shared_ptr不为空,会使原对象的引用计数减1
std::shared_ptr<int> p4 = std::make_shared<int>(2); //方式4
//使用方法例子:可以当作一个指针使用
std::cout << *p4 << std::endl;
//std::shared_ptr<int> p4 = new int(1);
if(p1) { //重载了bool操作符
std::cout << "p is not null" << std::endl;
}
int* p = p1.get();//获取原始指针
std::cout << *p << std::endl;
}
指定删除器
当使用shared_ptr删除数组时,需要指定删除器
常用的写法有以下几种
#include<iostream>
#include<memory>
template<typename T>
std::shared_ptr<T> make_shared_array(size_t size) {
return std::shared_ptr<T>(new T[size], std::default_delete<T[]>());
}
int main(){
std::shared_ptr<int> p(new int[10], [](int* p){delete [] p;}); //lambda
std::shared_ptr<int> p1(new int[10], std::default_delete<int[]>()); //指定默认删除器
std::shared_ptr<char> p2 = make_shared_array<char>(10); //自定义泛型方法
}
shared_ptr 共享指针是怎样计数的
共享指针,即多个指针指向同一个内存;具体实现方式是采用的引用计数,即这块地址上每多一个指针指向他,计数加一;
引用计数可以跟踪对象所有权,并能够自动销毁对象。可以说引用计数是个简单的垃圾回收体系。
std::shared_ptr 原理
智能指针是模板类而不是指针。创建一个智能指针时,必须指针可以指向的类型,<int>,<string> ……等。
智能指针实质就是重载了->和*操作符的类,由类来实现对内存的管理,确保即使有异常产生,也可以通过智能指针类的析构函数完成内存的释放。
具体来说它利用了引用计数技术和 C++ 的 RAII(资源获取就是初始化)特性。
先来说说 RAII,RAII 可以保证在任何情况下,使用对象时先构造对象,最后析构对象。
引用计数则是通过计算对裸指针引用次数来决定是否要释放这个指针对象,比如这个代码:
struct BigObj {
BigObj() {
std::cout << "big object has been constructed" << std::endl;
}
~BigObj() {
std::cout << "big object has been destructed" << std::endl;
}
};
std::shared_ptr<BigObj> sp1 = std::make_shared<BigObj>();
std::shared_ptr<BigObj> sp2 = sp1;
std::shared_ptr<BigObj> sp3 = sp2;
创建智能指针 sp1
时,该智能指针管理了一个 BigObj
的裸指针,sp1
内部有一个关于裸指针的引用计数,这时 sp1
的引用计数是 1,因为只有一个智能指针引用这个裸指针。当把 sp1
赋值给 sp2
时,有两个 shared_ptr
引用了该裸指针,因此引用计数就会加 1, std::shared_ptr<BigObj>
的引用计数就变为 2 了。同理,赋值给 sp3
时, std::shared_ptr<BigObj>
的引用计数就变为 3 了。
当引用计数减少为 0 时,智能指针就会去释放所引用的裸指针了。那么如何让引用计数减少呢?这里利用了 C++ 的 RAII 机制,我们只要在智能指针对象的析构函数里去减少引用计数就行了。
在 code2 目录下新建一个 code2.cpp 文件:
#include <iostream>
#include <memory>
struct BigObj {
BigObj() {
std::cout << "big object has been constructed" << std::endl;
}
~BigObj() {
std::cout << "big object has been destructed" << std::endl;
}
};
void test_ref() {
std::shared_ptr<BigObj> sp1 = std::make_shared<BigObj>();
std::cout << sp1.use_count() << std::endl;
std::shared_ptr<BigObj> sp2 = sp1;
std::cout << sp2.use_count() << std::endl;
std::shared_ptr<BigObj> sp3 = sp2;
std::cout << sp3.use_count() << std::endl;
std::cout << sp1.use_count() << std::endl;
}
void test_ref1() {
std::shared_ptr<BigObj> sp1 = std::make_shared<BigObj>();
std::cout << sp1.use_count() << std::endl;
{
std::shared_ptr<BigObj> sp2 = sp1;
std::cout << sp1.use_count() << std::endl;
}
std::cout << sp1.use_count() << std::endl;
BigObj* ptr = sp1.get();
sp1 = nullptr;
std::cout << sp1.use_count() << std::endl;
}
int main() {
test_ref();
test_ref1();
}
编译和运行代码:在 build 目录下执行
g++ ../code2.cpp -o code2 -std=c++11 && ./code2
输出结果:
big object has been constructed
1
2
3
3
big object has been destructed
big object has been constructed
1
2
1
big object has been destructed
0
我们可以清晰地看到引用计数增加和减少的情况,当减少为 0 的时候就会释放指针对象。
把 shared_ptr
设置为 nullptr
就可以让 shared_ptr
去释放所管理的裸指针。 通过 shared_ptr
的 get
方法可以获取它所管理的裸指针。
使用shared_ptr避免了手动使用delete来释放由new申请的资源,标准库也引入了make_shared函数来创建一个shared_ptr对象,使用shared_ptr和make_shared,你的代码里就可以使new和delete消失,同时又不必担心内存的泄露。shared_ptr是一个模板类。
每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候, 内存才会被释放。
可以通过构造函数、赋值函数或者make_shared函数初始化智能指针。(没有拷贝函数)
shared_ptr基于”引用计数”模型实现,多个shared_ptr可指向同一个动态对象,并维护一个共享的引用计数器,记录了引用同一对象的shared_ptr实例的数量。当最后一个指向动态对象的shared_ptr销毁时,会自动销毁其所指对象(通过delete操作符)。
shared_ptr的默认能力是管理动态内存,但支持自定义的Deleter以实现个性化的资源释放动作。
最安全的分配和使用动态内存的方法是调用一个名为make_shared的标准库函数。(关于make_shared:https://blog.csdn.net/bandaoyu/article/details/112197053)此函数在动态内存中分配一个对象并初始化它,返回指向此对象的shared_ptr。当要用make_shared时,必须指定想要创建的对象的类型,定义方式与模板类相同。在函数名之后跟一个尖括号,在其中给出类型。例如,调用make_shared<string>时传递的参数必须与string的某个构造函数相匹配。如果不传递任何参数,对象就会进行值初始化。 (make_shared<string>(“hello”))
当进行拷贝或赋值操作时,每个shared_ptr都会记录有多少个其它shared_ptr指向相同的对象。
可以认为每个shared_ptr都有一个关联的计数器,通常称其为引用计数(reference count)。无论何时拷贝一个shared_ptr,计数器都会递增。例如,当用一个shared_ptr初始化另一个shared_ptr,或将它作为参数传递给一个函数以及作为函数的返回值时,它所关联的计数器就会递增。当给shared_ptr赋予一个新值或是shared_ptr被销毁(例如一个局部的shared_ptr离开其作用域)时,计数器就会递减。一旦一个shared_ptr的计数器变为0,它就会自动释放自己所管理的对象。
当指向一个对象的最后一个shared_ptr被销毁时,shared_ptr类会自动销毁此对象。它是通过另一个特殊的成员函数析构函数(destructor)来完成销毁工作的。类似于构造函数,每个类都有一个析构函数。就像构造函数控制初始化一样,析构函数控制此类型的对象销毁时做什么操作。shared_ptr的析构函数会递减它所指向的对象的引用计数。如果引用计数变为0,shared_ptr的析构函数就会销毁对象,并释放它占用的内存。
如果将shared_ptr存放于一个容器中,而后不再需要全部元素,而只使用其中一部分,要记得用erase删除不再需要的那些元素。
std::shared_ptr使用注意事项
使用 shared_ptr
需要注意下面这几个问题:
- 不要让一个裸指针初始化多个
shared_ptr
; - 不要主动删除
shared_ptr
所管理的裸指针
BigObj *p = new BigObj();
std::shared_ptr<BigObj> sp(p);
std::shared_ptr<BigObj> sp1(p);
delete p;
这里让一个裸指针初始化两个 shared_ptr
,会导致该指针被删除两次,这是错误的。另外,既然我们让智能指针管理裸指针了,就不用再自己手动去删除改指针了,否则也会导致两次删除,应该让 shared_ptr
自动去删除所管理的裸指针。(所以使用shared_ptr的赋值都是shared_ptrA=shatred_ptrB。在创建智能指针时候都是shared_ptr ptr(new xxx),或者shared_ptr ptr.reset(new xxx),不能new创建了对象p再用shared_ptr指向p这种分步骤的)
实例:https://blog.csdn.net/zhangruijerry/article/details/100927531
3、不要在函数实参中创建shared_ptr。
如下面中的例子。
//不同编译器执行结果可能不同
//如果以new int -> 调用g() -> 创建shared_ptr的顺序
//那么假如g()方法失败,直接导致内存泄漏
void f(shared_ptr<int>(new int), g())
4、多线程读写 shared_ptr 要加锁
因为 shared_ptr 有两个数据成员,读写操作不能原子化。
详细解释:http://www.cppblog.com/Solstice/archive/2016/04/01/197597.html
std::shared_ptr使用注意事项2
- 不要用一个原始指针初始化多个shared_ptr
- 不要在函数实参中创建shared_ptr。
如下面中的例子。
//不同编译器执行结果可能不同
//如果以new int -> 调用g() -> 创建shared_ptr的顺序
//那么假如g()方法失败,直接导致内存泄漏
void f(shared_ptr<int>(new int), g())
- 通过shared_from_this()返回this指针时,不要作为shared_ptr返回,因为this是一个裸指针,可能会导致重复析构。
如下面例子中,sp1和sp2重复析构A对象,导致错误。如果需要返回this指针,可以通过继承enable_shared_from_this类,调用方法shared_from_this实现。如下面中注释掉的写法。
如果
#include<iostream>
#include<memory>
class A {
public:
std::shared_ptr<A> GetSelf() {
return std::shared_ptr<A>(this);
}
};
/*
class A :public std::enable_shared_from_this<A>{
public:
std::shared_ptr<A> GetSelf() {
return shared_from_this();
}
};
*/
int main(){
std::shared_ptr<A> sp1(new A);
std::shared_ptr<A> sp2 = sp1 -> GetSelf();
}
- 要注意循环引用带来的内存泄漏问题。如下面A与B循环引用,导致内存泄漏
#include<iostream>
#include<memory>
struct A;
struct B;
struct A {
std::shared_ptr<B> bptr;
~A() {
std::cout << "A is delete" << std::endl;
}
};
struct B {
std::shared_ptr<A> aptr;
~B() {
std::cout << "B is delete " << std::endl;
}
};
int main(){
std::shared_ptr<A> ap(new A);
std::shared_ptr<B> bp(new B);
ap->bptr = bp;
bp->aptr = ap;
}
链接:https://www.jianshu.com/p/d304cfa56ca0
- shared_ptr的类型转换不能使用一般的static_cast
shared_ptr的类型转换不能使用一般的static_cast,这种方式进行的转换会导致转换后的指针无法再被shared_ptr对象正确的管理。应该使用专门用于shared_ptr类型转换的
static_pointer_cast<T>()
const_pointer_cast<T>()
dynamic_pointer_cast<T>()
使用shared_ptr注意事项:
(1)、不要把一个原生指针给多个shared_ptr管理;
(2)、不要把this指针给shared_ptr;
(3)、不要在函数实参里创建shared_ptr;
(4)、不要不加思考地把指针替换为shared_ptr来防止内存泄漏,shared_ptr并不是万能的,而且使用它们的话也是需要一定的开销的;
(5)、环状的链式结构shared_ptr将会导致内存泄漏(可以结合weak_ptr来解决);
(6)、共享拥有权的对象一般比限定作用域的对象生存更久,从而将导致更高的平均资源使用时间;
(7)、在多线程环境中使用共享指针的代价非常大,这是因为你需要避免关于引用计数的数据竞争;
(8)、共享对象的析构器不会在预期的时间执行;
(9)、不使用相同的内置指针值初始化(或reset)多个智能指针;
(10)、不delete get()返回的指针;
(11)、不使用get()初始化或reset另一个智能指针;
(12)、如果使用get()返回的指针,记住当最后一个对应的智能指针销毁后,你的指针就变为无效了;
(13)、如果你使用智能指针管理的资源不是new分配的内存,记住传递给它一个删除器。
下图列出了shared_ptr支持的操作(来源于C++ Primer Fifth Edition 中文版):
下面是从其他文章中copy的测试代码,详细内容介绍可以参考对应的reference: