智能指针(一)
文章目录
- 智能指针(一)
- shared_ptr
- 1初始化方式
- make_ptr<>() 和 shared_ptr<>() 的区别
- shared_ptr维护引用计数需要的信息
- 使用原始的new函数创建shared_ptr
- 使用make_shared创建shared_ptr
- make_shared实现异常安全
- 使用make_shared的缺点
- make_shared 源码解析
- 2 引用计数的变化
- 引用计数的增加
- *将一个shared_ptr 作为构造函数参数 初始化 另一个 shared_ptr 指针*
- *将一个 shared ptr 作为函数参数 传递进去*
- 接收函数返回的 指向自身的 shared_ptr
- 引用计数不变
- *将一个 shared ptr 作为函数参数(引用传参的方式) 传递进去 引用计数不会增加*
- 引用计数减少
- *当 shared ptr 指向新的对象的时候,引用计数减一*
- *当 局部的shareptr 离开其生命域的时候,引用计数减为0;*
- 引用计数为0
- *当一个shared_ptri引用计数从1变成0,则它会自动释放自己所管理(指向)的对象;*
- 3 shared_ptr()的基本操作
- use_count()
- unique()
- reset()
- 不带参数
- 带参数
- 空指针使用reset()
- 使用解引用
- get()
- 注意:
- swap()
- nullptr()
- 智能指针名字作为判断条件
- 4 指定删除器
- 例子一
- *删除器可以用lambda 表达式*
- 自定义删除器
- *default_delete*
- shared<T [ ] >
- 仿函数自定义删除器
- 额外的例子
- 删除器补充
- 删除器补充
shared_ptr
1初始化方式
shared_ptr<int> makes(int values)
{
// return new int(values); // 无法将new 得到的int *转化为shared_ptr
return shared_ptr<int>(new int(values));
// 或者显式地使用int* 创建shared_ptr<int>
// return std::make_shared<int>(values);
};
void testFunction()
{
// 1采用构造函数初始化
shared_ptr<int> pi(new int(100));
// shared_ptr<int> pi2 = new int(100);// 智能指针是explicit的,阻止隐式类型转换
// 2使用make_shared()函数 推荐
// make_shared函数:标准库里的函数模板。安全,高效的分配和使用shared_ptr;
// 它能够在动态内存(堆)中分配并初始化一个对象,然后返回指向此对象的shared ptr;
shared_ptr<int> pi3 = make_shared<int>(100);
// 也可以使用类型推导简写代码
auto pi4 = make_shared<int>(100);
// 3使用函数返回值初始化
auto pi5 = makes(100);
// 4可以使用裸指针初始化,不推荐
int *pi6 = new int;
shared_ptr<int> pi7(pi6);
};
make_ptr<>() 和 shared_ptr<>() 的区别
shared_ptr维护引用计数需要的信息
- 强引用, 用来记录当前有多少个存活的 shared_ptrs 正持有该对象. 共享的对象会在最后一个强引用离开的时候销毁( 也可能释放).
- 弱引用, 用来记录当前有多少个正在观察该对象的 weak_ptrs. 当最后一个弱引用离开的时候, 共享的内部信息控制块会被销毁和释放 (共享的对象也会被释放, 如果还没有释放的话).
使用原始的new函数创建shared_ptr
- 首先是原始的new分配了原始对象, 然后将这个对象传递给 shared_ptr (即使用 shared_ptr 的构造函数) , shared_ptr 对象只能单独的分配控制块。
- 控制块包含被指向对象的引用计数以及其他,也就是说,控制块的内存是在std::shared_ptr的构造函数中分配的。
auto p = new widget();
shared_ptr sp1{ p }, sp2{ sp1 };
使用make_shared创建shared_ptr
- 如果选择使用 make_shared 的话, 内存分配的动作, 可以一次性完成,因为std::make_shared申请一个单独的内存块来同时存放指向的对象和控制块,这减少了内存分配的次数, 而内存分配是代价很高的操作。
- 同时,使用std::make_shared消除了一些控制块需要记录的信息,减少了程序的总内存占用。
auto sp1 = make_shared(), sp2{ sp1 };
make_shared实现异常安全
- 在shared_ptr的使用过程中,不能在函数实参中创建shared_ptr,如下:
//Define
void F(const std::shared_ptr<Lhs>& lhs, const std::shared_ptr<Rhs>& rhs)
{
;
}
//Call
F(std::shared_ptr<Lhs>(new Lhs("foo")),std::shared_ptr<Rhs>(new Rhs("bar")));
C++ 是不保证参数求值顺序, 以及内部表达式的求值顺序的, 所以可能的执行顺序如下:
new Lhs(“foo”))
new Rhs(“bar”))
std::shared_ptr<Lhs>
std::shared_ptr<Rhs>
如果在第2步的时候,发生了异常,第一步申请的 Lhs 对象内存就泄露了,
产生这个问题的核心在于, shared_ptr 没有立即获得裸指针,所以就有可能产生内存泄漏。当然,这个问题是可以这样解决:
auto lhs = std::shared_ptr<Lhs>(new Lhs("foo"));
auto rhs = std::shared_ptr<Rhs>(new Rhs("bar"));
F(lhs, rhs);
但,最推荐的做法是
F(std::make_shared<Lhs>("foo"), std::make_shared<Rhs>("bar"));
因为,申请原始对象和将原始对象裸指针赋值给shared_ptr是在同一个执行序列里,失败的话一起失败,成功就一起成功,这样就能保住创建的原始对象裸指针能安全的存放到std::shared_ptr中
使用make_shared的缺点
- 创建的对象如果没有公有的构造函数时,make_shared无法使用。
make_shared在传递参数格式是可变的,参数传递为生成类型的构造函数参数,因此在创建shared_ptr对象的过程中调用了类型T的某一个构造函数。
当然我们可以使用一些小技巧来解决这个问题, 比如这里 How do I call ::std::make_shared on a class with only protected or private constructors?
- 使用make_shared内存可能无法及时回收,对内存要求要的场景需要注意。
make_shared
只分配一次内存, 这看起来很好. 减少了内存分配的开销. 问题来了, weak_ptr
会保持控制块(强引用, 以及弱引用的信息)的生命周期, 而因此连带着保持了对象分配的内存, 只有最后一个 weak_ptr
离开作用域时, 内存才会被释放. 原本强引用减为 0 时就可以释放的内存, 现在变为了强引用, 若引用都减为 0 时才能释放, 意外的延迟了内存释放的时间. 这对于内存要求高的场景来说, 是一个需要注意的问题. 关于这个问题可以看这里 make_shared, almost a silver bullet
make_shared 源码解析
如下为make_shared的库函数实现版本:
template<typename _Tp, typename... _Args>
inline shared_ptr<_Tp>
make_shared(_Args&&... __args)
{
typedef typename std::remove_const<_Tp>::type _Tp_nc;
return std::allocate_shared<_Tp>(std::allocator<_Tp_nc>(),
std::forward<_Args>(__args)...);
}
template<typename _Tp, typename _Alloc, typename... _Args>
inline shared_ptr<_Tp>
allocate_shared(const _Alloc& __a, _Args&&... __args)
{
return shared_ptr<_Tp>(_Sp_make_shared_tag(), __a,
std::forward<_Args>(__args)...);
}
我们依次分析上述的关键代码
//关键行1
template<typename _Tp, typename... _Args>
inline shared_ptr<_Tp> make_shared(_Args&&... __args)
//关键行2
std::forward<_Args>(__args)...
//关键行3
return shared_ptr<_Tp>(_Sp_make_shared_tag(), __a,
std::forward<_Args>(__args)...);
- 从上述关键代码可以看出:make_shared是组合使用可变参数模板与forward(转发)机制实现将实参保持不变地传递给其他函数。如最开始的string例子**。**
1)使用可变参数:是因为string有多个构造函数,且参数各不相同; - 2)Args参数为右值引用(Args&&)和std::forward:是为了保持实参中类型信息的传递。这样当传递一个右值string&& 对象给make_shared时,就可以使用string的移动构造函数进行初始化。注意,两者必须结合使 用,缺一不可;
- 此外std::forward<_Args>(__args)…是采用包扩展形式调用的,原理如下:
shared_ptr<string> p1 = make_shared<string>(10, '9');
//扩展如下,对两个参数分别调用
std::forward
return shared_ptr<string>(_Sp_make_shared_tag(), _a ,
std::forward<int>(10),
std::forward<char>(c));
补充说明:
①模板参数为右值引用,采用引用折叠原理:
1)参数为左值时,实参类型为普通的左值引用; T& &, T&& &,T& && =>T&
2)参数为右值时,实参类型为右值: T&& && => T&&
②std::forward:是一个模板,通过显示模板实参来调用,调用后forward返回显示实参类型的右值引用。即,forward的返回类型为T&&,在根据上述引用折叠原理即可保存参数是左值还是右值类型;比如:
int i = 0;
std::forward<int>(i), i将以int&传递
std::forward<int>(42), 42将以int&&传递
std::forward实现代码:
/**
* @brief Forward an lvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type& __t) noexcept
{ return static_cast<_Tp&&>(__t); }
/**
* @brief Forward an rvalue.
* @return The parameter cast to the specified type.
*
* This function is used to implement "perfect forwarding".
*/
template<typename _Tp>
constexpr _Tp&&
forward(typename std::remove_reference<_Tp>::type&& __t) noexcept
{
static_assert(!std::is_lvalue_reference<_Tp>::value, "template argument"
" substituting _Tp is an lvalue reference type");
return static_cast<_Tp&&>(__t);
}
2 引用计数的变化
引用计数的增加
将一个shared_ptr 作为构造函数参数 初始化 另一个 shared_ptr 指针
// 1 将一个shared_ptr 作为构造函数参数 初始化 另一个 shared_ptr 指针
void func1()
{
auto pi = make_shared<int>(100);
cout << "pi.useCount()=" << pi.use_count() << endl;
auto pi2(pi);
// pi和pi2指向了相同的对象, pi的引用计数+1; pi和 pi2 共享对象,所以引用计数都是2
cout << "pi.useCount()=" << pi.use_count() << " "
<< "pi2.useCout()=" << pi2.use_count() << endl;
// 每个shared ptr都会记录有多少个其他的shared pt指向相同的对象;
}
将一个 shared ptr 作为函数参数 传递进去
void F(shared_ptr<int> ptr) // ptr 和传入的pi 指向 同一个对象,引用计数加一
{
cout << "ptr.use_count()=" << ptr.use_count() << endl;
// ptr 在结束生命区域后,会被回收,所以ptr的引用计数会减一
return;
}
void func2()
{
auto pi = make_shared<int>(100);
cout << "pi.useCount()=" << pi.use_count() << endl;
F(pi);
cout << "pi.useCount()=" << pi.use_count() << endl;
}
接收函数返回的 指向自身的 shared_ptr
shared_ptr<int> FF(shared_ptr<int> &ptr) // 传引用作为参数,引用计数不会增加
{
cout << "ptr.use_count()=" << ptr.use_count() << endl;
return ptr; // 左常引用和右值引用都可以把临时对象的生命周期延长到与引用自身的生命周期相同
// ptr的生命周期将被延长,将不会在FF 函数结束之后被回收
// 这里的ptr 对象的 就相当于是 pi 对象(因为传引用)
}
void func3()
{
auto pi = make_shared<int>(100);
cout << "pi.useCount()=" << pi.use_count() << endl;
auto ret = FF(pi); // 引用计数变成2
// FF(pi);// 如果不接收返回值,也就是ret 没有指向 ptr对象(也就是pi 对象)则引用数 会变成 1
cout << "pi.useCount()=" << pi.use_count() << endl;
}
引用计数不变
将一个 shared ptr 作为函数参数(引用传参的方式) 传递进去 引用计数不会增加
// 将一个 shared ptr 作为函数参数 传递进去
void F(shared_ptr<int> &ptr) // ptr 传引用参数,引用计数将不会增加
{
cout << "ptr.use_count()=" << ptr.use_count() << endl;
//
return;
}
void func2()
{
auto pi = make_shared<int>(100);
cout << "pi.useCount()=" << pi.use_count() << endl;
F(pi);
cout << "pi.useCount()=" << pi.use_count() << endl;
}
引用计数减少
当 shared ptr 指向新的对象的时候,引用计数减一
shared_ptr<int> pi(new int);
shared_ptr<int> pi2(pi);
shared_ptr<int> pi3(pi);
void func()
{
cout << "pi.useCount()=" << pi.use_count() << endl;
cout << "pi2.useCount()=" << pi2.use_count() << endl;
cout << "pi3.useCount()=" << pi3.use_count() << endl;
pi = make_shared<int>(100); // pi指向新的对象,引用计数为1, p2 p3 的引用计数减一
cout << "pi.useCount()=" << pi.use_count() << endl;
cout << "pi2.useCount()=" << pi2.use_count() << endl;
cout << "pi3.useCount()=" << pi3.use_count() << endl;
}
当 局部的shareptr 离开其生命域的时候,引用计数减为0;
shared_ptr<string> func2()
{
return make_shared<string>("null");
// 离开作用域之后,引用计数将减少为0
};
引用计数为0
当一个shared_ptri引用计数从1变成0,则它会自动释放自己所管理(指向)的对象;
auto pi = make_shared<int>(100);
auto pi2 = make_shared<int>(100);
pi = pi2; // 把 pi2 赋值给 pi , pi将和pi2共同指向一个对象; pi原先指向的对象 被释放; pi 的引用计数和 pi2的 引用计数为 2
cout << "pi.cout=" << pi.use_count() << endl;
cout << "pi2.cout=" << pi2.use_count() << endl;
3 shared_ptr()的基本操作
use_count()
// 1) user_count() 显示share_ptr 的引用计数
unique()
// 2) unique()是否该智能指针独占某个指向的对像,也就是若只有一个智能指针指向某个对象,unique()返回true
auto pi = make_shared<int>(100);
auto pi2 = make_shared<int>(100);
pi = pi2; // 把 pi2 赋值给 pi , pi将和pi2共同指向一个对象; pi原先指向的对象 被释放; pi 的引用计数和 pi2的 引用计数为 2
if (pi.unique())
{
cout << "pi独占对象" << endl;
}
else
{
cout << "pi共享对象" << endl;
}
reset()
不带参数
- 若p是唯一指向该对象的指针,那么释放p所指向的对象,并将pi置空
- 若p不是唯一指向该对象的指针,那么不释放p所指向的对象,但指向该对象的引用计数会减少1,同时将置空 *
auto pi = make_shared<int>(100);
auto pi3 = make_shared<int>(100);
cout << "pi3: usecout=" << pi3.use_count() << endl;
pi3.reset();
pi.reset();
cout << "pi3: usecout=" << pi3.use_count() << endl;
cout << "pi: usecout=" << pi.use_count() << endl;
cout << "================================================================" << endl;
带参数
- reset()带参数(一般是一个new出来的指针)时
- 若p是唯一指向该对象的指针,则释放p指向的对象,让p指向新对像。
- 若p不是唯一指向该对象的指针,则不释放p指向的对象,但指向该对象的引用计数会减少1,同时让p指向新对象:
auto pi4 = make_shared<int>(100);
auto pi5(pi4);
cout << "pi4: usecout=" << pi4.use_count() << endl;
cout << "pi5: usecout=" << pi5.use_count() << endl;
pi4.reset(new int(200)); // 不释放原内存; 原内存的引用计数减一, pi4 指向新内存;
cout << "pi4: usecout=" << pi4.use_count() << endl;
cout << "pi5: usecout=" << pi5.use_count() << endl;
if (pi4.unique() && pi5.unique())
{
cout << "pi4 pi5独占对象" << endl;
}
else
{
cout << "pi4 pi5共享对象" << endl;
}
空指针使用reset()
shared_ptr<int>p;
p.reset(neit(1); //释放p所指向的对象,让p指向新对象,因为原来p为空的,所以就等于啥也没释放直接指向新对象;*
使用解引用
auto pi6 = make_shared<int>(500);
cout << "pi6 = " << *pi6 << endl;
get()
- 考虑到有些函数的修数需要的是一个内置裸指针而不是智能指针。
- p.get():返回中保存的指针(裸指针),小心使用,如果智能指针释放了所指向的对象,那么这个返回的裸指针也就失效.
int *point = pi6.get();
*point = 600;
cout << "pi6 = " << *pi6 << endl;
注意:
- 不能使用delete 来回收get() 返回的裸指针
delete point;
swap()
- 交换两个智能指针所指向的对象
shared_ptr<double> Rp(new double(100.00));
shared_ptr<double> Lp(new double(520.00));
cout << "Lp = " << *Lp << endl;
cout << "Rp = " << *Rp << endl;
Rp.swap(Lp);
// swap(Lp,Rp); 或者使用这个函数
cout << "Lp = " << *Lp << endl;
cout << "Rp = " << *Rp << endl;
nullptr()
- 将所指向的对象引用计数减1,若引用计数变为0,则释放智能指针所指向的对象。
- 将智能指针置空
shared_ptr<string> Np(new string("null"));
auto pp(Np);
cout << "Np.useCout = " << Np.use_count() << endl;
cout << "pp.useCout = " << pp.use_count() << endl;
Np = nullptr; // Np 置空; pp指向的对象 的引用计数减一
cout << "Np.useCout = " << Np.use_count() << endl;
cout << "pp.useCout = " << pp.use_count() << endl;
智能指针名字作为判断条件
if (Np)
{
cout << "Np 指针指向一个对象" << endl;
}
else
{
cout << "Np 指针指向空" << endl;
}
4 指定删除器
- 一定时机帮我们删除所指向的对象;delete:将delete运算符号作为默认的资源析构方式。
- 我们可以指定自己的删除器取代系统提供的默认删除器,当智能指针需要删除所指向的对象时,编译器就会调用我们自己的删除器
- shared ptr指定删除器方法比较简单,一般只需要在参数中添加具体的删除器函数名即可;
例子一
void myDelete(int *p) // 删除整型对象用
{
delete p;
cout << "detail5::myDelete is Used" << endl;
}
void func()
{
// auto pi = make_shared<int>(new int(100),myDelete); // make_shared 函数不能 指定删除器
shared_ptr<int> pi2(new int(100), myDelete);
}
删除器可以用lambda 表达式
void func2()
{
shared_ptr<int> pi3(new int(100), [](int *p)
{ cout<<"detail5::func2()"<<endl;
delete p; });
};
自定义删除器
- 默认删除器处理不了(用shared ptri管理动态数组),需要我们提供自己指定的删刚除器:
void func3()
{
shared_ptr<int> pi4(new int[10](), [](int *p)
{cout<<"detail5:: func3()"<<endl;
delete[] p; });
}
default_delete
- 可用default_delete来做删除器,default_.delete是标准库里的模板类。
void fun4()
{
cout << "detail5:: func4()" << endl;
shared_ptr<int> pi5(new int[100](), std::default_delete<int[]>());
}
shared<T [ ] >
也可以使用 ,定义数组的时候我们在尖括号中加[],定义模板参数是数组
void func5()
{
cout << "detail5:: func5()" << endl;
shared_ptr<int[]> pi5(new int[100]());
}
仿函数自定义删除器
//用来释放malloc出来的函数对象
template<class T>
class FreeFunc{
public:
void operator()(T* ptr)
{
cout << "free:" << ptr << endl;
free(ptr);
}
};
//用来释放new[]出来的函数对象
template<class T>
class DeleteArrayFunc {
public:
void operator()(T* ptr)
{
cout << "delete[]" << ptr << endl;
delete[] ptr;
}
};
//用来释放new 出来的函数对象
template<class T>
class DeleteArrayFunc {
public:
void operator()(T* ptr)
{
cout << "delete " << ptr << endl;
delete ptr;
}
};
//用来释放文件描述符的函数对象
template<class T>
class ClosefdFunc{
public:
void operator()(T* fd)
{
cout << "close fd" << fd << endl;
fclose(fd);
}
};
void test06(){
FreeFunc<int> Object1;
shared_ptr<int> sp1((int*)malloc(sizeof(int)*4), Object1); // 回调函数是可调用对象,可以是普通的函数名或者函数对象或者lambda表达式
DeleteArrayFunc<int> Object2;
shared_ptr<int> sp2(new int[4], Object2);
ClosefdFunc<FILE> Object3;
shared_ptr<FILE> sp3(fopen("myfile.txt","w"), Object3);
}
int main()
{
test06();
return 0;
}
参见我的 另一篇 <仿函数>
额外的例子
class A
{
public:
A() = default; //
~A()
{
cout << "~A()" << endl;
}
};
void func()
{
// 使用lambda 自定义删除器
shared_ptr<A> p(new A[10], [](A *p) // 对象数组
{cout<<"detail6::func()"<<endl;
delete[] p; });
}
void func2()
{
cout << "detail6:: func2()" << endl;
// 可用default_delete来做删除器,default_.delete是标准库里的模板类。
shared_ptr<A> p2(new A[10], std::default_delete<A[]>()); // 模板参数是对象数组
}
// 也可以使用 ,定义数组的时候我们在尖括号中加[],定义模板参数是对象数组
void func3()
{
cout << "detail6:: func3()" << endl;
shared_ptr<A[]> p2(new A[10]);
}
删除器补充
- 就算是两个shared ptr指定了不同的删除器,只要他们所指向的对象类型相同,那么这两个shared ptr也属于同一个类型
void func()
{
auto lambda1 = [](int *p)
{
cout << "detail7::func() lambda1" << endl;
delete p;
};
auto lambda2 = [](int *p)
{
cout << "detail7::func() lambda2" << endl;
delete p;
};
shared_ptr<int> p1(new int(100), lambda1);
shared_ptr<int> p2(new int(100), lambda2);
// /类型相同,就代表可以放到元素类型为该对象类型的容器里来;
vector<shared_ptr<int>> vc{p1, p2};
}
- 也可以使用 ,定义数组的时候我们在尖括号中加[],定义模板参数是对象数组
void func3()
{
cout << "detail6:: func3()" << endl;
shared_ptr<A[]> p2(new A[10]);
}
删除器补充
- 就算是两个shared ptr指定了不同的删除器,只要他们所指向的对象类型相同,那么这两个shared ptr也属于同一个类型
void func()
{
auto lambda1 = [](int *p)
{
cout << "detail7::func() lambda1" << endl;
delete p;
};
auto lambda2 = [](int *p)
{
cout << "detail7::func() lambda2" << endl;
delete p;
};
shared_ptr<int> p1(new int(100), lambda1);
shared_ptr<int> p2(new int(100), lambda2);
// /类型相同,就代表可以放到元素类型为该对象类型的容器里来;
vector<shared_ptr<int>> vc{p1, p2};
}