目录
前言
智能指针
shared_ptr共享的智能指针
shared_ptr的基本用法
使用shared_ptr要注意的问题
weak_ptr弱引用的智能指针
weak_ptr的基本用法
weak_ptr返回this指针
weak_ptr解决循环引用问题
weak_ptr使用注意事项
匿名函数lambda
匿名函数lambda
捕获列表
匿名函数的简写
前言
本文重点是C++11智能指针与匿名函数
本专栏知识点是通过零声教育的线上课学习,进行梳理总结写下文章,对c/c++linux课程感兴趣的读者,可以点击链接 C/C++后台高级服务器课程介绍 详细查看课程的服务。
智能指针
C++程序设计中使用堆内存是非常频繁的操作,堆内存的申请和释放都由程序员自己管理。程序员自己 管理堆内存可以提高了程序的效率,但是整体来说堆内存的管理是麻烦的,C++11中引入了智能指针的 概念,方便管理堆内存。使用普通指针,容易造成堆内存泄露(忘记释放),二次释放,程序发生异常 时内存泄露等问题等,使用智能指针能更好的管理堆内存。
C++里面的四个智能指针: auto_ptr,** unique_ptr,shared_ptr, weak_ptr** 其中后三个是C++11支持, 并且第一个已经被C++11弃用。
shared_ptr共享的智能指针
std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。再最后一个shared_ptr析 构的时候,内存才会被释放。
shared_ptr共享被管理对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个 shared_ptr对象销毁时,被管理对象自动销毁。
简单来说,shared_ptr实现包含了两部分,
一个指向堆上创建的对象的裸指针,raw_ptr
一个指向内部隐藏的、共享的管理对象。share_count_object
shared_ptr的基本用法
1. 初始化
通过构造函数、std::shared_ptr辅助函数和reset方法来初始化shared_ptr,代码如下:
std::shared_ptrp1(new int(1)); std::shared_ptr p2 = p1; std::shared_ptr p3; p3.reset(new int(1)); if(p3) { cout << "p3 is not null"; }
我们应该优先使用make_shared来构造智能指针,因为他更高效。
auto sp1 = make_shared(100); //相当于 shared_ptr sp1(new int(100));
不能将一个原始指针直接赋值给一个智能指针,例如,下面这种方法是错误的:
std::shared_ptrp = new int(1);
shared_ptr不能通过“直接将原始这种赋值”来初始化,需要通过构造函数和辅助方法来初始化。 对于一个未初始化的智能指针,可以通过reset方法来初始化,当智能指针有值的时候调用reset会引起 引用计数减1。
另外智能指针可以通过重载的bool类型操作符来判断。
// 1-1-reset-count #include#include using namespace std; int main() { std::shared_ptr p1; p1.reset(new int(1)); std::shared_ptr p2 = p1; // 引用计数此时应该是2 cout << "p2.use_count() = " << p2.use_count()<< endl; p1.reset(); cout << "p1.reset()n"; // 引用计数此时应该是2 cout << "p2.use_count()= " << p2.use_count() << endl; if(!p1) { cout << "p1 is emptyn"; } if(!p2) { cout << "p2 is emptyn"; } p2.reset(); cout << "p2.reset()n"; cout << "p2.use_count()= " << p2.use_count() << endl; if(!p2) { cout << "p2 is emptyn"; } return 0; }
2. 获取原始指针
当需要获取原始指针时,可以通过get方法来返回原始指针,代码如下所示:
std::shared_ptrptr(new int(1)); int *p = ptr.get(); // 不小心 delete p;
谨慎使用p.get()的返回值,如果你不知道其危险性则永远不要调用get()函数。 p.get()的返回值就相当于一个裸指针的值,不合适的使用这个值,上述陷阱的所有错误都有可能发生, 遵守以下几个约定:
- 不要保存p.get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的
- 保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针
- 不要delete p.get()的返回值 ,会导致对一块内存delete两次的错误
3. 指定删除器
如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器。
//1-1-delete #include#include using namespace std; void DeleteIntPtr(int *p) { cout << "call DeleteIntPtr" << endl; delete p; } int main() { std::shared_ptr p(new int(1), DeleteIntPtr); return 0; }
当p的引用计数为0时,自动调用删除器DeleteIntPtr来释放对象的内存。删除器可以是一个lambda表达 式,上面的写法可以改为:
std::shared_ptrp(new int(1), [](int *p) { cout << "call lambda delete p" << endl; delete p;});
当我们用shared_ptr管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器不支持数组对象,代码如下所示:
std::shared_ptrp3(new int[10], [](int *p) { delete [] p;});
使用shared_ptr要注意的问题
1. 不要用一个原始指针初始化多个shared_ptr
2. 不要在函数实参中创建shared_ptr
因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右,所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建, 则int内存泄漏了,正确的写法应该是先创建智能指针
3. 通过shared_from_this()返回this指针。
不要将this指针作为shared_ptr返回出来,因为this指针本质上是一个裸指针,因此,这样可能会导致重复析构
4. 避免循环引用。循环引用会导致内存泄漏
weak_ptr弱引用的智能指针
share_ptr智能指针还是有内存泄露的情况,当两个对象相互使用一个shared_ptr成员变量指向对方,会造成循环引用,使引用计数失效,从而导致内存泄漏。weak_ptr不是单独使用,需要和shared_ptr一起使用。
weak_ptr 是一种不控制对象生命周期的智能指针, 它指向一个 shared_ptr 管理的对象. 进行该对象的内存管理的是那个强引用的shared_ptr, weak_ptr只是提供了对管理对象的一个访问手段。weak_ptr 设计的目的是为配合 shared_ptr 而引入的一种智能指针来协助 shared_ptr 工作, 它只可以从一个shared_ptr 或另一个 weak_ptr 对象构造, 它的构造和析构不会引起引用记数的增加或减少。weak_ptr是用来解决shared_ptr相互引用时的死锁问题,如果说两个shared_ptr相互引用,那么这两个指针的引用计数永远不可能下降为0,资源永远不会释放。它是对对象的一种弱引用,不会增加对象的引用计数,和shared_ptr之间可以相互转化,shared_ptr可以直接赋值给它,它可以通过调用lock函数来获得shared_ptr。
weak_ptr没有重载操作符*和->,因为它不共享指针,不能操作资源,主要是为了通过shared_ptr获得资源的监测权,它的构造不会增加引用计数,它的析构也不会减少引用计数,纯粹只是作为一个旁观者来监视shared_ptr中管理的资源是否存在。weak_ptr还可以返回this指针和解决循环引用的问题。
weak_ptr的基本用法
1. 通过use_count()方法获取当前观察资源的引用计数,如下所示:
shared_ptrsp(new int(10)); weak_ptr wp(sp); cout << wp.use_count() << endl; //结果讲输出1
2. 通过expired()方法判断所观察资源是否已经释放,如下所示:
shared_ptrsp(new int(10)); weak_ptr wp(sp); if(wp.expired()) cout << "weak_ptr无效,资源已释放"; else cout << "weak_ptr有效";
3. 通过lock方法获取监视的shared_ptr,如下所示:
lock有什么用处?
std::weak_ptrgw; void f() { if(gw.expired()) { cout << "gw无效,资源已释放"; } else { auto spt = gw.lock(); cout << "gw有效, *spt = " << *spt << endl; } } int main() { { auto sp = atd::make_shared (42); gw = sp; f(); } f(); return 0; }
weak_ptr返回this指针
shared_ptr章节中提到不能直接将this指针返回shared_ptr,需要通过派生std::enable_shared_from_this类,并通过其方法shared_from_this来返回指针,原因是std::enable_shared_from_this类中有一个weak_ptr,这个weak_ptr用来观察this智能指针,调用shared_from_this()方法是,会调用内部这个weak_ptr的lock()方法,将所观察的shared_ptr返回,再看前面的范例
#include#include using namespace std; class A: public std::enable_shared_from_this { public: shared_ptrGetSelf() { return shared_from_this(); // } ~A() { cout << "Deconstruction A" << endl; } }; int main() { shared_ptr sp1(new A); shared_ptr sp2 = sp1->GetSelf(); // ok return 0; }
输出结果如下:
Deconstruction A
在外面创建A对象的智能指针和通过对象返回this的智能指针都是安全的,因为shared_from_this()是内部的weak_ptr调用lock()方法之后返回的智能指针,在离开作用域之后,spy的引用计数减为0,A对象会被析构,不会出现A对象被析构两次的问题。
需要注意的是,获取自身智能指针的函数尽在shared_ptr的构造函数被调用之后才能使用,因为enable_shared_from_this内部的weak_ptr只有通过shared_ptr才能构造。
weak_ptr解决循环引用问题
在shared_ptr章节提到智能指针循环引用的问题,因为智能指针的循环引用会导致内存泄漏,可以通过weak_ptr解决该问题,只要将A或B的任意一个成员变量改为weak_ptr
#include#include using namespace std; class A; class B; class A { public: std::weak_ptr bptr; // 修改为weak_ptr ~A() { cout << "A is deleted" << endl; } }; class B { public: std::shared_ptr aptr; ~B() { cout << "B is deleted" << endl; } }; int main() { { std::shared_ptr ap(new A); std::shared_ptr bp(new B); ap->bptr = bp; bp->aptr = ap; } cout<< "main leave" << endl; return 0; }
这样在对B的成员赋值时,即执行bp->aptr=ap;时,由于aptr是weak_ptr,它并不会增加引用计数,所以ap的引用计数仍然会是1,在离开作用域之后,ap的引用计数为减为0,A指针会被析构,析构后其内部的bptr的引用计数会被减为1,然后在离开作用域后bp引用计数又从1减为0,B对象也被析构,不会发生内存泄漏。
weak_ptr使用注意事项
1. weak_ptr在使用前需要检查合法性。
weak_ptrwp; { shared_ptr sp(new int(1)); //sp.use_count()==1 wp = sp; //wp不会改变引用计数,所以sp.use_count()==1 shared_ptr sp_ok = wp.lock(); //wp没有重载->操作符。只能这样取所指向的对象 } shared_ptr sp_null = wp.lock(); //sp_null .use_count()==0;
因为上述代码中sp和sp_ok离开了作用域,其容纳的K对象已经被释放了。
得到了一个容纳NULL指针的sp_null对象。在使用wp前需要调用wp.expired()函数判断一下。
因为wp还仍旧存在,虽然引用计数等于0,仍有某处“全局”性的存储块保存着这个计数信息。直到最后一个weak_ptr对象被析构,这块“堆”存储块才能被回收。否则weak_ptr无法直到自己所容纳的那个指针资源的当前状态。
匿名函数lambda
匿名函数lambda
//[捕获列表](参数列表)->返回类型{函数体}
int main()
{
auto Add = [](int a, int b)->int {
return a + b;
};
std::cout << Add(1, 2) << std::endl; //输出3
return 0;
}
//[捕获列表](参数列表)->返回类型{函数体}
int main()
{
auto Add = [](int a, int b)->int {
return a + b;
};
std::cout << Add(1, 2) << std::endl; //输出3
return 0;
}
一般情况下,编译器可以自动推断出lambda表达式的返回类型,所以我们可以不指定返回类型,即:
//[捕获列表](参数列表){函数体}
int main()
{
auto Add = [](int a, int b) {
return a + b;
};
std::cout << Add(1, 2) << std::endl; //输出3
return 0;
}
但是如果函数体内有多个return语句时,编译器无法自动推断出返回类型,此时必须指定返回类型。
捕获列表
有时候,需要在匿名函数内使用外部变量,所以用捕获列表来传参,如
int main()
{
int c = 12;
auto Add = [c](int a, int b)->int {
//捕获列表加入使用的外部变量c,否则无法通过编译
return c;
};
std::cout << Add(1, 2) << std::endl;
return 0;
}
但是,如果Add中加入一句:c = a;
int main()
{
int c = 12;
auto Add = [&c](int a, int b)->int {
//捕获列表改为了&c,表示按引用传递,就可以修改了;不加&表示按值传递,无法通过编译
c = a;
return c;
};
std::cout << Add(1, 2) << std::endl;
return 0;
}
补充知识:
1. 如果捕获列表为[&],则表示所有的外部变量都按引用传递给lambda使用;
2. 如果捕获列表为[=],则表示所有的外部变量都按值传递给lambda使用;
3. 匿名函数构建的时候对于按值传递的捕获列表,会立即将当前可以取到的值拷贝一份作为常数,然后将该常数作为参数传递。
匿名函数的简写
匿名函数由捕获列表、参数列表、返回类型和函数体组成;可以忽略参数列表和返回类型,但不可以忽略捕获列表和函数体,如:
auto f = []{ return 1 + 2; };
Lambda捕获列表



