C++智能指针、绑定器和函数对象、lambda表达式
智能指针
智能指针可以保证资源的自动释放
不带引用计数的智能指针
auto_ptr只让最后一个指向的指针管理资源,之前的auto_ptr会被置为nullptr
scoped_ptr删除了拷贝构造和赋值重载函数
unique_ptr:推荐使用,也删除了拷贝构造和赋值重载函数,但是提高了右值引用的拷贝构造和赋值重载
unique_ptr<int> ptr(new int); unique_ptr<int> ptr2 = std::move(ptr); // 使用了右值引用的拷贝构造 ptr2 = std::move(ptr); // 使用了右值引用的operator=赋值重载函数
语义明确
带引用计数的智能指针
shared_ptr和weak_ptr标记几个在使用该指针的数量shared_ptr和weak_ptr底层的引用计数已经通过CAS操作,保证了引用计数加减的原子特性,因此shared_ptr和weak_ptr本身就是线程安全的带引用计数的智能指针。
shared_ptr:强智能指针可以改变资源的引用技术
weak_ptr:若智能指针 不会改变资源的引用技术
强智能指针的交叉引用是什么问题?什么结果?怎么解决?
#include <iostream> #include <memory> using namespace std; class B; // 前置声明类B class A { public: A() { cout << "A()" << endl; } ~A() { cout << "~A()" << endl; } shared_ptr<B> _ptrb; // 指向B对象的智能指针 }; class B { public: B() { cout << "B()" << endl; } ~B() { cout << "~B()" << endl; } shared_ptr<A> _ptra; // 指向A对象的智能指针 }; int main() { shared_ptr<A> ptra(new A());// ptra指向A对象,A的引用计数为1 shared_ptr<B> ptrb(new B());// ptrb指向B对象,B的引用计数为1 ptra->_ptrb = ptrb;// A对象的成员变量_ptrb也指向B对象,B的引用计数为2 ptrb->_ptra = ptra;// B对象的成员变量_ptra也指向A对象,A的引用计数为2 cout << ptra.use_count() << endl; // 打印A的引用计数结果:2 cout << ptrb.use_count() << endl; // 打印B的引用计数结果:2 /* 出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和 B对象的引用计数从2减到1,达不到释放A和B的条件(释放的条件是 A和B的引用计数为0),因此造成两个new出来的A和B对象无法释放, 导致内存泄露,这个问题就是“强智能指针的交叉引用(循环引用)问题” */ return 0; }
代码打印结果:
A()
B()
2
2
可以看到,A和B对象并没有进行析构,通过上面的代码示例,能够看出来“交叉引用”的问题所在,就是对象无法析构,资源无法释放,那怎么解决这个问题呢?请注意强弱智能指针的一个重要应用规则:定义对象时,用强智能指针shared_ptr,在其它地方引用对象时,使用弱智能指针weak_ptr。
弱智能指针weak_ptr区别于shared_ptr之处在于:
- weak_ptr不会改变资源的引用计数,只是一个观察者的角色,通过观察shared_ptr来判定资源是否存在
- weak_ptr持有的引用计数,不是资源的引用计数,而是同一个资源的观察者的计数
- weak_ptr没有提供常用的指针操作,无法直接访问资源,需要先通过lock方法提升为shared_ptr强智能指针,才能访问资源
那么上面的代码怎么修改,也就是如何解决带引用计数的智能指针的交叉引用问题,代码如下:
#include <iostream> #include <memory> using namespace std; class B; // 前置声明类B class A { public: A() { cout << "A()" << endl; } ~A() { cout << "~A()" << endl; } weak_ptr<B> _ptrb; // 指向B对象的弱智能指针。引用对象时,用弱智能指针 }; class B { public: B() { cout << "B()" << endl; } ~B() { cout << "~B()" << endl; } weak_ptr<A> _ptra; // 指向A对象的弱智能指针。引用对象时,用弱智能指针 }; int main() { // 定义对象时,用强智能指针 shared_ptr<A> ptra(new A());// ptra指向A对象,A的引用计数为1 shared_ptr<B> ptrb(new B());// ptrb指向B对象,B的引用计数为1 // A对象的成员变量_ptrb也指向B对象,B的引用计数为1,因为是弱智能指针,引用计数没有改变 ptra->_ptrb = ptrb; // B对象的成员变量_ptra也指向A对象,A的引用计数为1,因为是弱智能指针,引用计数没有改变 ptrb->_ptra = ptra; cout << ptra.use_count() << endl; // 打印结果:1 cout << ptrb.use_count() << endl; // 打印结果:1 /* 出main函数作用域,ptra和ptrb两个局部对象析构,分别给A对象和 B对象的引用计数从1减到0,达到释放A和B的条件,因此new出来的A和B对象 被析构掉,解决了“强智能指针的交叉引用(循环引用)问题” */ return 0; }
绑定器和函数对象
bind1st:operator()中第一个形参变量绑定成一个确定的值
bind2st:operator()中第二个形参变量绑定成一个确定的值
绑定器+二元函数对象->一元函数对象
auto it1 = find_if(vec.begin(),vec.end(), bind1st(greater<int>(),70);) //find_if寻找第一个小于70
void hello1(){ cout << "hello world!"<< endl; } function<void> func1 = hello1; func1();//"hello world!" function<int(int,int)> func2 = [](int a,int b)->int {return a+b};
function
std::function还有一个特别有意思的用法,你可以将一个重载了()操作符的对象赋值给它,这样就可以像调用函数一样使用该对象了。
... struct A { void operator()() { std::cout << "This is A Object" << std::endl; } }; ... int main(...){ ... A a; func = a; func(); ... }
模板的完全特例化:所有参数都标记上
模板的部分特例化:例如只表明是指针,没表明是哪种类型的指针
bind绑定器,绑定参数
void hello ( string str) { cout << str << endl; } int sum ( int a, int b){ return a + b; } bind (hello, "hello bind ! ")(); cout <<bind (sum, 10,20)() <<endl; //参数占位符绑定器出了语句,无法继续使用 bind (hello,_1) ( "hello bind 2 ! " ); cout << bind (sum,_1,_2) (200,300 )<<endl; //此处把bind返回的绑定器binder就复用起来了 function<void(string) > func1 = bind (hello,_1); func1 ( "hello china ! " ) ; func1 ( "hello shan xi ! " ); func1 ( "hello si chuan ! " );
lambda表达式
lambada表达式的语法
//[捕获外部变量](形参列表)->返回值{操作代码};
auto func1 = []()->void {cout << "hello world!" << endl;};
如果不需要返回值那么->void可以省略
->可以省略(1):如果function body中存在return语句,则该Lambda表达式的返回类型由return语句的返回类型确定; (2):如果function body中没有return语句,则返回值为void类型。
捕获外部变量
[]不捕获任何外部变量
[=]传值捕获
[&]传引用捕获
[this]捕获外部的this指针
[=,&a]引用捕获a其余传值捕获
int a = 10; int b = 20; auto func3 = [&a,&b]() { int tmp = a; a = b b = tmp; }; func3(); //a=20 b=10
lambada表达式->函数对象function
map<int,function<int(int,int)>> caculateMap; caculateMap[1] = [](int a, int b)->int{return a + b;}; caculateMap[2] = [](int a, int b)->int{return a - b;}; caculateMap[3] = [](int a, int b)->int{return a * b;}; caculateMap[4] = [](int a, int b)->int{return a / b;};
修改捕获变量
前在Lambda表达式中,如果以传值方式捕获外部变量,则函数体中不能修改该外部变量,否则会引发编译错误。那么有没有办法可以修改值捕获的外部变量呢?这是就需要使用mutable关键字,该关键字用以说明表达式体内的代码可以修改值捕获的变量,示例:
int a = 123; auto f = [a]()mutable { cout << ++a; }; // 不会报错 输出124
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· DeepSeek 开源周回顾「GitHub 热点速览」
· 记一次.NET内存居高不下排查解决与启示
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· .NET10 - 预览版1新功能体验(一)