C++智能指针
前置实战

这里面有一个new T的类(避免修改返回类的类型直接使用模板类)有一个simplesharePtr的模板类,
其中一个成员T是所管理成员对象
另一个是成员指向控制块
每当多一个simplesharePtr的模板类指向newT(),int ref_count就会加1
struct ControlbBlock {
int ref_count;
ControlbBlock():ref_count(1){}
};
template <typename T>
class SimpleSharePtr {
private:
T* ptr;//指向管理的对象
ControlbBlock* control;//指向控制块
public;
SimpleSharePtr():ptr(nullptr), control(nullptr){}
explicit SimpleSharePtr(T* p):ptr(p){//显示构造
if (p) {//如果传进来一个空指针控制块就是空,要传进来东西就要开辟空间给控制块了new一下
control = new ControlbBlock();
}
else {
control = nullptr;
}
}
};
思路:先创建一个模板类,对象是一个指向对象的地址,一个是控制计数的控制块,控制块有计数器,所以创建一个控制块的结构体,里面有计数器和把计数置为1的计数器
共有部分:完成构造函数的时候需要接收外部指针,所以接受T*p,如果外部指针非空就新开辟空间给控制块,否则控制块就置为空。
这里为什么要新弄一个控制块的结构体,里面放计数器
因为控制块是要放在公共的位置,如果放在类里,每次拷贝都是从头开始计数
接下来如果计数器为0了就要释放newT()这个空间
所以对象新加一个release这个函数
private:
T* ptr;//指向管理的对象
ControlbBlock* control;//指向控制块
void relesase() {
if (control) {//control不为空证明指向了计数器
--control -> ref_count;//让计数器减1
if (control->ref_count == 0) {
delete ptr;
ptr = nullptr;
deletecontrol;
control = nullptr;
}
}
}
public;
explicit SimpleSharePtr(T* p):ptr(p){
if (p) {//如果传进来一个空指针控制块就是空,要传进来东西就要开辟空间给控制块了new一下
control = new ControlbBlock();
}
else {
control = nullptr;
}
}
~SimpleSharePtr() {
if (ptr) {//ptr不为空证明分配了内存,就要释放它
release();//释放的操作要看控制块有没有指向计数器
}
}
这里就是在上面的基础上新建了一个release函数用于当析构的时候减计数器的值并且判断如果计数器如果为零了证明没人去使用这个被管理的对象,销毁这个被管理的对象
拷贝构造
SimpleSharePtr(count SimpleSharePtr& s) :ptr(s.ptr), control(s.control) {
if (control) {
++ control->ref_count;
}
}
接下来是类似于赋值,把一个智能指针赋值给另一个智能指针
//SimpleSharePtr s2(new student()); s2 = s1
这里就是把s1管理的东西也给s2,先断开s2和student的联系并且判断自己是不是最后一个指向student的指针从而判断需不需要释放这块内存
SimpleSharePtr& operator=(const SimpleSharePtr& s) {
if (this != &s) {
relesase();
ptr = s.ptr;
control = s.control;
if (control) {
++control->ref_count;
}
}
return *this//隐含的this指针
}
const SimpleSharePtr& s 中有&这是标准写法,传递复杂对象用引用可以避免拷贝
这里有隐含的this指针
判断是不是自赋值,不是的话进行与原来断舍离,新复制,++计数器
接下里是移动构造,之前那个废掉(转移资源)
//移动相当于废掉原来的那个
SimpleSharePtr(SimpleSharePtr&& other)noexpect:ptr(other.ptr), control(other.control) {
other.ptr = nullptr;
other.control = nullptr;
}
移动赋值
SimpleSharePtr & operator=(SimpleSharePtr&& other)noexpect {
if (this != &other) {
release();
ptr = s.ptr;
control = s.control;
other.ptr = nullptr;
other.control = nullptr;
}
return *this;
}
其中&&是右值引用
| 项目 | Value |
|---|---|
| SimpleSharePtr& operator=(SimpleSharePtr&& other) | 移动赋值,接收右值,转移资源所有权 |
| SimpleSharePtr& operator=(const SimpleSharePtr& s) | 拷贝赋值,接收左值,增加资源引用计数 |
//SimpleSharePtr s2(new student()); s2->name = “tom”
修改问我这个模板指向对象的内容
T* operator->()const
{
return ptr;
}
解引用
(s2.ptr->name) = “tmo”
T* operator*()const{
return *ptr;
}
返回一个裸指针Stduent* raw_s2 = s2.get()
T* get*()const{
return *ptr;
}
返回有多少引用计数
三目运算符记忆法(是吗?不是选中间若是选后边)
int use_count()const{
return control ? control->ref_count :0;
}
reset:重置指针,指向新对象或 nullptr。
void reset(T* p = nullptr) {
// 释放当前资源
release();
// 指向新资源
ptr = p;
if (p) {
control = new ControlBlock();
} else {
control = nullptr;
}
std::shared_ptr
前面我们简单实现了shared_ptr,但其实C++已经给我们封装好了
std::shared_ptr主要特性:
● 共享所有权: 多个 shared_ptr 可以指向同一个对象。
● 引用计数: 跟踪有多少 shared_ptr 实例指向同一对象。
● 自动释放:当引用计数为0时,自动释放资源。
std unipue_ptr
std::unique_ptr 是一种独占所有权的智能指针,任何时刻只能有一个 unique_ptr 实例拥有对某个对象的所有权。不能被拷贝,只能被移动。
在后期常用于管理std::mutext std::thread singleton boost::asio iocontext,这些概念在后期
| 项目 | Value |
|---|---|
| std::mutext | 互斥锁,线程同步 |
| std::thread | 线程创建和管理 |
| singleto | 只创建一个实例的设计模式 |
| boost::asio iocontext | 异步 I/O 核心执行上下文 |
构造函数与复制
● 默认构造函数: 创建一个空的 unique_ptr。
● 指针构造函数: 接受一个裸指针,拥有其所有权。
● 移动构造函数: 将一个 unique_ptr 的所有权转移到另一个 unique_ptr。
● 移动赋值操作符: 将一个 unique_ptr 的所有权转移到另一个 unique_ptr。
使用实例
std::unipue_ptr<Student> ptr1(new Student("zack",25))
ptr1->print()//可以直接调动类里面的函数
}结束的时候自动析构
std::unipue_ptr<Student> ptr2 = std::move(ptr1);//移动操作ptr1里面都没了
ptr1 = std::unipue_ptr<Student>(new Student("z123",25))//新建的给ptr1
有时候new出来的对象赋值给了一个临时变量,后来临时变量在别的线程里面把那块内存释放掉了,此时又有智能指针指向这里的内存
std::unique_ptr<Student> ptr3 = std::make_unique<Student>("Vivo",25);
//这里临时创建一个智能指针右值然后move给左值避免了裸指针的出现
//临时创建的智能指针是右值,所有权能借助移动语义安全转移给左值变量,整个过程避免了裸指针直接暴露,从而大大降低内存管理风险。
面试小题:当一个智能指针move另给另外一个智能指针的时候,他本身的析构会触发吗?
当一个智能指针通过 std::move 将所有权转移给另一个智能指针时,
被移动的智能指针自身的析构函数会在其生命周期结束时正常调用,
但是它内部的管理指针已经被置为 nullptr,
所以它析构时不会释放资源。
真正拥有资源的智能指针析构时,才会释放对应资源
std::weak_ptr
std::weak_ptr 是一种不拥有对象所有权的智能指针,用于观察但不影响对象的生命周期。主要用于解决 shared_ptr 之间的循环引用问题
● 非拥有所有权: 不增加引用计数。
● 可从 shared_ptr 生成: 通过 std::weak_ptr 可以访问(辅助) shared_ptr 管理的对象。
● 避免循环引用: 适用于双向关联或观察者模式。
上面三点也是面试中问到weak_ptr作用
循环引用的概念:循环引用是两个对象通过智能指针互相持有,引用计数永远不为0造成内存泄漏,std::weak_ptr通过不增加引用计数来打破这个环。
#include <iostream>
#include <memory>
class B; // 前向声明
class A {
public:
std::shared_ptr<B> ptrB;
A() { std::cout << "A Constructor" << std::endl; }
~A() { std::cout << "A Destructor" << std::endl; }
};
class B {
public:
std::shared_ptr<A> ptrA;
B() { std::cout << "B Constructor" << std::endl; }
~B() { std::cout << "B Destructor" << std::endl; }
};
int main() {
{
std::shared_ptr<A> a = std::make_shared<A>();
std::shared_ptr<B> b = std::make_shared<B>();
a->ptrB = b;
b->ptrA = a;
}
std::cout << "Exiting main..." << std::endl;
return 0;
}
输出: A Constructor
B Constructor
Exiting main…
注意:如果没有 a->ptrB = b;
b->ptrA = a;的话,将会正常析构,因为ab里面的智能指针被编译器置为nullptr,最后结束的时候不管他正常调用析构。但是一旦这个之智能指针指向彼此了,里面有东西了,那么就循环引用了,结束的时候不会调用析构
所以出现了weak_ptr
class B {
public:
std::weak_ptr<A> ptrA;
B() { std::cout << "B Constructor" << std::endl; }
~B() { std::cout << "B Destructor" << std::endl; }
};
std::weak_ptr<A> ptrA; 不占用A的引用计数,只是能访问里面的数据
weak_ptr 不能直接访问对象,需要通过 lock() 方法转换为 shared_ptr,并检查对象是否仍然存在
#include <iostream>
#include <memory>
int main() {
std::shared_ptr<int> sp = std::make_shared<int>(42);
std::weak_ptr<int> wp = sp;
if (auto locked = wp.lock()) { // 尝试获取 shared_ptr
std::cout << "Value: " << *locked << std::endl;
} else {
std::cout << "Object no longer exists." << std::endl;
}
sp.reset(); // 释放资源
if (auto locked = wp.lock()) { // 再次尝试获取 shared_ptr
std::cout << "Value: " << *locked << std::endl;
} else {
std::cout << "Object no longer exists." << std::endl;
}
return 0;
}
自定义删除器
有时,默认的 delete 操作不适用于所有资源管理场景。此时,可以使用自定义删除器来指定资源释放的方式。例如,管理文件句柄、网络资源或自定义清理逻辑
#include <iostream>
#include <memory>
#include <cstdio>
struct FileDeleter {
void operator()(FILE* fp) const {
if (fp) {
std::cout << "Closing file." << std::endl;
fclose(fp);
}
}
};
int main() {
{
//
std::shared_ptr<FILE> filePtr(fopen("example.txt", "w"), FileDeleter());
if (filePtr) {
std::cout << "File opened successfully." << std::endl;
// 使用 filePtr 进行文件操作
fprintf(filePtr.get(), "Hello, World!\n");
}
} // 自动关闭文件
std::cout << "Exiting main..." << std::endl;
return 0;
}
注意不能用a = std::make::_shared<A>();这样的话就没法用这个自定义的删除器了
第二种使用 Lambda 表达式作为删除器
auto fileDeletor = [](FILE* fp){
if(fp){
fclose(fp);
}
}//[]表示捕获的列表,()表示接受的参数
std::unipue_ptr<FILE,decltype(fileDeletor)> filePtr(fopen("txet.txt","w",fileDeletor));//lambda是一个模板类,需要尖括号
if(filePtr){
fprintf(filePtr,GET(),"HELLO");
}
练习题1使用std::unipue_ptr管理动态资源要求:
1. 类定义:
○ 创建一个名为 ResourceManager 的类。
○ 添加一个私有成员变量,类型为 std::unique_ptr,用于管理动态分配的整数。
2. 构造函数与析构函数:
○ 实现构造函数,接受一个整数参数并初始化 std::unique_ptr。
○ 不需要显式定义析构函数,依赖 std::unique_ptr 自动释放资源。
3. 成员函数:
○ int getValue() const;
返回当前管理的整数值。
○ void setValue(int newValue);
设置整数的新值。
4. 禁止拷贝:
○ 禁用拷贝构造函数和拷贝赋值运算符,确保 ResourceManager 对象不能被拷贝。
○ 允许移动构造和移动赋值运算符。
5. 测试:
○ 编写一个 main 函数,创建 ResourceManager 对象,设置并获取值,展示智能指针的作用。
1.智能指针包含的头文件#include <memory>
2.创建一个类class名字叫做 ResourceManager class ResourceManager {};
里面有成员函数和公共部分private: public:
3.成员函数 std::unique_ptr<int> resource;是一个智能模板类,类似于int* resource
4.ResourceManager用类本身的名字(构造函数)构造的时候不都是给个对象在初始化一下,这时候不是给个值吗ResourceManager a1(10)这么构造
5.getValue;获取的整数需要有一个int类型来接受,第二不需要参数因为他也不去改变什么,const是说不能让这个对象里的变量改变,int val = res.getValue()在test函数里面是这么用的(假设要取res这个对象当前状态下管理的整数)
6. = delete表示禁用这个函数ResourceManager(const ResourceManager&)同样用类名去构造,接受的函数是一个const不可被修饰的ResourceManager&表示左值引用表示已经创建好的对象(和&&的区别是&&表示移动的,移动完之后会被销毁)
7.记住即可赋值构造ResourceManager& 后要加上&,作用支持链式赋值,避免拷贝开销 赋值构造得有运算符重载operator= 后面接受的参数是右值引用(ResourceManager&)
后面的移动部分的代码几乎同理
//.h
#include <memory>
class ResourceManager {
private:
std::unique_ptr<int> resource;//resource 的成员变量,它的类型是 std::unique_ptr<int>
public:
explicit ResourceManager(int value);
int getValue() const;//返回当前管理的整数值。
void setValue(int newValue);//设置整数的新值。
ResourceManager(const ResourceManager&) = delete;//禁止拷贝
ResourceManager& operator=(const ResourceManager&) = delete;//禁止拷贝赋值
ResourceManager(ResourceManager&& other) ;//允许移动
ResourceManager& operator=(ResourceManager&& other) ;//允许移动赋值
};
头文件构思完成开始完成各个函数的具体实现
1.先给头文件#include "ResourceManager.h"#include <stdexcept> // 用于抛异常
2.头文件explicit ResourceManager(int value);实现的思路有固定格式类名::函数名(参数列表): {...} 所以ResourceManager:: ResourceManager(int value):resoure(std::make_make_unique<int>(value)){} 引号后面是初始化(std::make_unique<int>(value)用这个make_uique去构造初始化智能指针
3.头文件int getValue() const;实现:首先我要去获取整形的值肯定返回类型是int,函数体里面要判断一下当前智能指针有东西吗if (resource)有值的话里面是一个对象的指针,我们解引用一下才行并且返回return *resource
4.头文件void setValue(int newValue);改变东西,捕获的东西不需要有什么返回的东西,因为改变的操作在函数体里面就完成了,并且我要改变东西就有条件来告诉我有什么条件void ResourceManager::setValue(int newValue) 放到这里就是我要改变当前的值就要接受一个新的值,此后结构和获取值逻辑一样,判断当前有东西吗,然后把这个值newvalue赋值给解引用之后的resoure
5.头文件ResourceManager(ResourceManager&&)实现:ResourceManager::ResourceManager(ResourceManager&& other) noexceptnoexcept表示不会抛异常
这函数体内的我们需要移动它们的值,这里直接利用移动构造resource(std::move(other.resource))
6.函数体内的主要逻辑时要用到等于号这个概念所以先判断他们相等吗if(*this !=&other)相等的话直接返回return *this如果不相等的话则resource = std::move(other.resource)
#include "ResourceManager.h"
#include <stdexcept> // 用于抛异常
ResourceManager::ResourceManager(int value)
: resource(std::make_unique<int>(value)) {
// 构造时resource指向新申请的int,值为value
}
int ResourceManager::getValue() const {
if (resource) {
return *resource; // 返回int值
}
throw std::runtime_error("Resource is null"); // 指针nullptr抛异常
}
void ResourceManager::setValue(int newValue) {
if (resource) {
*resource = newValue; // 重新设置int值
} else {
throw std::runtime_error("Resource is null");
}
}
ResourceManager::ResourceManager(ResourceManager&& other) noexcept
: resource(std::move(other.resource)) {
// 移动构造完成,other.resource变为nullptr
}
ResourceManager& ResourceManager::operator=(ResourceManager&& other) noexcept {
if (this != &other) {
resource = std::move(other.resource);
}
return *this;
}
测试函数
#include <iostream>
#include "ResourceManager.h"
void printValue(const ResourceManager& rm, const std::string& name) {
try {
std::cout << name << " value: " << rm.getValue() << std::endl;
} catch (const std::exception& e) {
std::cout << name << " caught exception: " << e.what() << std::endl;
}
}
int main() {
// 1. 构造 ResourceManager,初始化值为10
ResourceManager rm1(10);
printValue(rm1, "rm1"); // 输出 rm1 value: 10
// 2. 移动构造 rm2 从 rm1 移动资源
ResourceManager rm2(std::move(rm1));
printValue(rm2, "rm2"); // 输出 rm2 value: 10
printValue(rm1, "rm1"); // rm1资源被移动,应报异常或空资源
// 3. 赋值新值给 rm2
rm2.setValue(20);
printValue(rm2, "rm2"); // 输出 rm2 value: 20
// 4. 移动赋值,rm3 初始值30
ResourceManager rm3(30);
printValue(rm3, "rm3"); // 输出 rm3 value: 30
rm3 = std::move(rm2); // 移动赋值,rm3 获得 rm2 资源
printValue(rm3, "rm3"); // 输出 rm3 value: 20
printValue(rm2, "rm2"); // rm2资源被移动,应报异常或空资源
return 0;
}
可调用对象
函数指针
//函数用函数指针来调用
#include <iostream>
int (*funcptr)(int, int);//(*funcptr)函数指针指向函数
int add(int a, int b)
{
return a + b;
}
};
int main() {
//函数指针指向函数
funcptr = add;//或者&add,
int result = funcptr(1, 2);
std::cout << result << std::endl;
return 0;
}
//优点:简单直观,适用于简单的回调函数。
//缺点:不能捕获上下文
// 2语法复杂
仿函数
仿函数(又称函数对象)在C++中重载operator()
实例1
#include <iostream>
// 定义一个仿函数类
struct Adder {
int to_add;
// 构造函数
Adder(int value) : to_add(value) {}
// 重载()运算符
int operator()(int x) const {//接受x
return x + to_add;
}
};
int main() {
Adder add5(5); // 创建一个添加5的仿函数
std::cout << "10 + 5 = " << add5(10) << std::endl; // 输出: 10 + 5 = 15
return 0;
}
1. 携带状态: 仿函数可以拥有内部状态,通过成员变量存储数据,使其在调用时具备上下文信息。
2. 灵活性高: 可以根据需要添加更多的成员函数和变量,扩展功能。
3. 性能优化: 编译器可以对仿函数进行优化,例如内联展开,提高执行效率。
实例2
模仿比较算法,find_if(numbers.begin(), numbers.end(), greaterThan10)获取数组的头和尾和greaterThan10的值进行比较
#include <iostream>
#include <vector>
#include <algorithm>
// 仿函数:判断一个数是否大于某个阈值
struct IsGreaterThan {
int threshold;
IsGreaterThan(int t) : threshold(t) {}
bool operator()(int x) const {
return x > threshold;
}
};
int main() {
std::vector<int> numbers = {1, 5, 10, 15, 20};
// 使用仿函数进行筛选
IsGreaterThan greaterThan10(10);
auto it = std::find_if(numbers.begin(), numbers.end(), greaterThan10);
if(it != numbers.end()) {
std::cout << "第一个大于10的数是: " << *it << std::endl; // 输出: 第一个大于10的数是: 15
} else {
std::cout << "没有找到大于10的数。" << std::endl;
}
return 0;
}
仿函数和模板
#include <iostream>
#include <vector>
#include <algorithm>
// 通用比较仿函数
template <typename T>
struct Compare {
bool operator()(const T& a, const T& b) const {
return a < b;
}
};
int main() {
std::vector<int> numbers = {5, 2, 8, 1, 9};
// 使用仿函数进行排序
std::sort(numbers.begin(), numbers.end(), Compare<int>());//首先接受5,2比较然后变成2 5
std::cout << "排序后的数字: ";
for(auto num : numbers) {
std::cout << num << " "; // 输出: 1 2 5 8 9
}
std::cout << std::endl;
return 0;
}
知识点补充:基于范围的for循环
for (声明变量 : 容器或数组) {
// 循环体,使用变量访问当前元素
}
声明变量 用于接收容器中当前元素的副本或引用。
每次循环,变量会自动依次指向容器中的下一个元素。
循环会自动遍历完所有元素,无需使用下标或迭代器。
Lambda
Lambda表达式是C++11引入的一种轻量级函数对象,允许在代码中定义匿名函数。它们可以捕获周围的变量,具有更强的表达能力。
[captures](parameters) -> return_type {
// 函数体
}
● captures: 捕获外部变量的方式,可以是值捕获、引用捕获或者混合捕获。
● parameters: 参数列表。
● return_type: 返回类型,可以省略,编译器会自动推导。
● 函数体: 实际执行的代码
实例
#include <iostream>
#include <vector>
#include <algorithm>
int main() {
int threshold = 5;
std::vector<int> numbers = {1, 6, 3, 8, 2, 7};
// 使用lambda表达式进行过滤
numbers.erase(std::remove_if(numbers.begin(), numbers.end(),
[threshold](int n) -> bool {
return n < threshold;
}), numbers.end());
// 输出结果
for(auto n : numbers) {
std::cout << n << " "; // 输出: 6 8 7
}
return 0;
}
lambda表达式 [threshold](int n) -> bool { return n < threshold; } :这是谓词函数,捕获了外部变量 threshold,对容器中的每个元素 n 做判断,返回 true 表示该元素满足删除条件(即小于5的元素)。
std::remove_if 把所有小于threshold的元素“移到”容器末尾,返回不满足删除条件元素之后的位置。
numbers.erase(new_end, numbers.end());从那个位置开始到末尾全部删除,实现了删除所有小于 threshold 的元素。
// 使用lambda表达式进行过滤
numbers.erase(std::remove_if(numbers.begin(), numbers.end(),
[threshold](int n) -> bool {
return n < threshold;
}), numbers.end());
捕获方式:
1. 值捕获 ([=]): 捕获所有外部变量的副本。
2. 引用捕获 ([&]): 捕获所有外部变量的引用。
3. 混合捕获: 指定部分变量按值捕获,部分按引用捕获,如 [=, &var] 或 [&, var]。
4. 无捕获 ([]): 不捕获任何外部变量。
int temp = 2;
auto lambda = [=,&temp](int x){
temp*=x;//注意这里对其他值捕获对temp进行引用捕获,因为这里引用的是一个副本,要修改副本的值要用引用捕获
}
lambda(3);
std::cout<<temp<<std::endl;//打印6
在实际使用中建议要修改什么就引用捕获什么,针对上边代码更常见的是,新建一个指针指向副本的地址,然后【】内部直接放上地址,在后边操作解引用即可
int * ptemp = &temp;
auto lambda1 = [ptemp](int x){
*ptemp*=x;
};
lambda表达式和智能指针结合
看下面一段代码,我们创建一个智能指针交给add_ptr管理然后利用lambda表达式去捕获,最后打印的时候显示这个智能指针的引用计数为2
在异步或者回调中,如果智能指针管理的那块空间在别的线程中释放掉了,这时候lambda表达式还要使用这块内存,这样就很危险
而用了智能指针捕获的话,就不会发生这种现象,因为在lambda表达式中捕获,智能指针里面的计数器不为0可以放心调用,不会被系统回收。
#include <iostream>
#include <vector>
#include<algorithm>
#include<memory>
struct Adder {
int to_add;
Adder(int value) :to_add(value) {}
int operator()(int x) {//前面构造定义to-add,后面括号里接受x完成加法
return x + to_add;
}
void add(int x) {
to_add += x;
}
};
struct IsGreaterThan {
int threshould;
IsGreaterThan(int value) :threshould(value) {}//构造初始化
bool operator()(int x) {//接受x
return x > threshould;//比较x和初始化的对象
}
};
int main() {
auto add_ptr = std::make_shared<Adder>(10);
auto lambda2 = [add_ptr](int x) {
add_ptr->add(x);
std::cout << add_ptr.use_count() << std::endl;
};
lambda2(5);
return 0;
}
再看一个例子下边代码里t1.detach();会在程序中异步执行,主线程不会等待他结束,这就使得代码块里面的程序结束后add_ptr管理的对象会被释放,这似乎后由于使用lambda表达式捕获,引用计数不为0,所以这块空间还没被释放
#include <iostream>
#include <memory>
#include <thread>
struct Adder {
int to_add;
Adder(int value) : to_add(value) {}
void add(int x) {
to_add += x;
}
};
int main() {
int temp = 2;
std::cout << temp << std::endl;
{
auto add_ptr = std::make_shared<Adder>(10);
auto lambda2 = [add_ptr](int x) {
add_ptr->add(x);
std::cout << add_ptr.use_count() << std::endl;
};
std::thread t1(lambda2, 5);
t1.detach(); // 线程在后台执行
} // 这里代码块结束,add_ptr局部变量销毁(但智能指针引用计数可能>0)
return 0;
}
可变Lambda
之前lambda捕获的时候 修改时外部的副本也跟着修改,
可变lambda修改的只是lambda内的副本,不影响外部的 count 变量
#include <iostream>
int main() {
int count = 0;
auto increment = [count]() mutable {
count++;
std::cout << "Count inside Lambda: " << count << std::endl;
};
increment(); // 输出: Count inside Lambda: 1
increment(); // 输出: Count inside Lambda: 2
std::cout << "Count outside Lambda: " << count << std::endl; // 输出: Count outside Lambda: 0
return 0;
}
捕获成员函数和成员变量
#include <iostream>
#include <vector>
#include <algorithm>
class Processor {
public:
Processor(int threshold) : threshold(threshold) {}
void process(std::vector<int>& data) {
std::cout << "处理前数据: ";
for(auto num : data) std::cout << num << " ";
std::cout << std::endl;
// 使用Lambda表达式进行过滤
data.erase(std::remove_if(data.begin(), data.end(),
[this](int n) -> bool {
return n < threshold;
}), data.end());
std::cout << "处理后数据: ";
for(auto num : data) std::cout << num << " ";
std::cout << std::endl;
}
private:
int threshold;
};
int main() {
std::vector<int> numbers = {1, 6, 3, 8, 2, 7};
Processor proc(5);
proc.process(numbers);
/*
输出:
处理前数据: 1 6 3 8 2 7
处理后数据: 6 8 7
*/
return 0;
}
std::function 对象
std::function 是C++11提供的一个通用的可调用包装器,能够封装任何可调用对象,包括普通函数、Lambda表达式、函数对象以及绑定表达式。它实现了类型擦除,使得不同类型的可调用对象可以通过统一的接口进行操作。
<返回值类型(接受值类型,接受值类型)>
#include <iostream>
#include <functional>
// 普通函数
int add(int a, int b) {
return a + b;
}
// 函数对象
struct Multiply {
int operator()(int a, int b) const {
return a * b;
}
};
int main() {
// 封装普通函数
std::function<int(int, int)> func1 = add;
std::cout << "Add: " << func1(3, 4) << std::endl; // 输出: Add: 7
// 封装Lambda表达式
std::function<int(int, int)> func2 = [](int a, int b) -> int {
return a - b;
};
std::cout << "Subtract: " << func2(10, 4) << std::endl; // 输出: Subtract: 6
// 封装函数对象
Multiply multiply;
std::function<int(int, int)> func3 = multiply;
std::cout << "Multiply: " << func3(3, 4) << std::endl; // 输出: Multiply: 12
return 0;
}
● 类型擦除: 可以存储任何符合签名的可调用对象。(不关注调用对象的类型,不管是普通函数函数对象还是lambda表达式)
● 灵活性: 支持动态改变存储的可调用对象。(在上述中不使用func2,而是复用func1,这是可以的)
● 性能开销: 相比于直接使用函数指针或Lambda,std::function 可能带来一定的性能开销,尤其是在频繁调用时。
用法场景
● 回调函数的传递。
● 事件处理系统。
● 策略模式的实现。
//定义回调函数
using Callback = std::function<void(int)>;
//std::function 是C++11提供的通用函数包装器,可以存储任意可调用对象(函数指针、lambda、仿函数等)。
//这里定义 Callback 为接受 int 参数且无返回值的函数类型,统一回调接口。
//2. 触发事件的函数
void triggerEvent(Callback cb, int value) {
cb(value); // 关键:调用传入的回调函数
}
//参数:接收一个回调函数 cb 和一个整数值 value。
//行为:当事件发生时(这里简化成直接调用),用 value 触发回调
//1) Lambda表达式作为回调
triggerEvent([](int x) {
std::cout << "事件触发,值为: " << x << std::endl;
}, 42);
//触发流程:
//Lambda被隐式转换为 std::function<void(int)> 类型。
//triggerEvent 内部调用 cb(42),实际执行Lambda。
//(2) 仿函数(Functor)作为回调
struct Printer {
void operator()(int x) const {
std::cout << "Printer打印值: " << x << std::endl;
}
} printer;
triggerEvent(printer, 100);
//触发流程:
//printer 对象隐式转换为 std::function。
//triggerEvent 调用 cb(100),实际执行 printer.operator()(100)。
#include <iostream>
#include <functional>
// 普通函数
int add(int a, int b) {
return a + b;
}
// 函数对象
struct Multiply {
int operator()(int a, int b) const {
return a * b;
}
};
int main() {
// 封装普通函数
std::function<int(int, int)> func1 = add;
std::cout << "Add: " << func1(3, 4) << std::endl; // 输出: Add: 7
// 封装Lambda表达式
std::function<int(int, int)> func2 = [](int a, int b) -> int {
return a - b;
};
std::cout << "Subtract: " << func2(10, 4) << std::endl; // 输出: Subtract: 6
// 封装函数对象
Multiply multiply;
std::function<int(int, int)> func3 = multiply;
std::cout << "Multiply: " << func3(3, 4) << std::endl; // 输出: Multiply: 12
return 0;
}
std::bind的用法
std::bind 是C++11中提供的一个函数适配器,用于绑定函数或可调用对象的部分参数,生成一个新的可调用对象。它允许提前固定某些参数,简化函数调用或适应接口需求。
#include <iostream>
#include <functional>
// 普通函数
int add(int a, int b) {
return a + b;
}
int main() {
// 绑定第一个参数为10,生成新的函数对象
auto add10 = std::bind(add, 10, std::placeholders::_1);//第一个是绑定的对象
//第二个是想绑定的参数//第三个是占位符
//意思就是把add第一个参数绑定成10,第二个参数不动用占位符占着
std::cout << "10 + 5 = " << add10(5) << std::endl; // 输出: 10 + 5 = 15
return 0;
}
占位符 (std::placeholders)
std::bind 使用**占位符来表示未绑定的参数,**这些占位符决定了在生成的新函数对象中如何传递参数。
常用的占位符包括:
● std::placeholders::_1
● std::placeholders::_2
● std::placeholders::_3
● 等等,根据需要传递的参数数量。
#include <iostream>
#include <functional>
void display(const std::string& msg, int count) {
for(int i = 0; i < count; ++i) {
std::cout << msg << std::endl;
}
}
int main() {
// 绑定消息为"Hello",生成新的函数对象,只需要传递次数
auto sayHello = std::bind(display, "Hello", std::placeholders::_1);
sayHello(3);
/*
输出:
Hello
Hello
Hello
*/
return 0;
}
与lambda表达式相比
std::bind 曾在C++11中广泛使用,但随着Lambda表达式的普及,很多情况下Lambda更为直观和高效。不过,在某些复杂的参数绑定场景下,std::bind 依然有其独特优势。
#include <iostream>
#include <functional>
int multiply(int a, int b) {
return a * b;
}
int main() {
// 绑定第一个参数为2,生成新的函数对象
auto multiplyBy2 = std::bind(multiply, 2, std::placeholders::_1);
std::cout << "2 * 5 = " << multiplyBy2(5) << std::endl; // 输出: 2 * 5 = 10
return 0;
}
#include <iostream>
#include <functional>
int multiply(int a, int b) {
return a * b;
}
int main() {
// 使用Lambda表达式绑定第一个参数为2
auto multiplyBy2 = [](int b) -> int {
return multiply(2, b);
};
std::cout << "2 * 5 = " << multiplyBy2(5) << std::endl; // 输出: 2 * 5 = 10
return 0;
}
绑定类的成员函数
#include <iostream>
#include <functional>
class Calculator {
public:
int multiply(int a, int b) const {
return a * b;
}
};
int main() {
Calculator calc;
// 绑定成员函数multiply,固定第一个参数为5
auto multiplyBy5 = std::bind(&Calculator::multiply, &calc, 5, std::placeholders::_1);
std::cout << "5 * 3 = " << multiplyBy5(3) << std::endl; // 输出: 5 * 3 = 15
return 0;
}
使用Lambda表达式绑定成员函数
#include <iostream>
#include <functional>
class Greeter {
public:
void greet(const std::string& name) const {
std::cout << "Hello, " << name << "!" << std::endl;
}
};
int main() {
Greeter greeter;
// 使用Lambda表达式绑定成员函数
auto greetFunc = [&greeter](const std::string& name) {
greeter.greet(name);
};
greetFunc("Alice"); // 输出: Hello, Alice!
return 0;
}
绑定静态成员函数
#include <iostream>
#include <functional>
class Logger {
public:
static void log(const std::string& message) {
std::cout << "Log: " << message << std::endl;
}
};
int main() {
// 使用std::bind绑定静态成员函数
auto logFunc = std::bind(&Logger::log, std::placeholders::_1);
logFunc("This is a static log message."); // 输出: Log: This is a static log message.
return 0;
}
绑定带有返回值的成员函数
#include <iostream>
#include <functional>
class Math {
public:
double power(double base, double exponent) const {
double result = 1.0;
for(int i = 0; i < static_cast<int>(exponent); ++i) {
result *= base;
}
return result;
}
};
int main() {
Math mathObj;
// 绑定成员函数power,固定基数为2
auto powerOf2 = std::bind(&Math::power, &mathObj, 2.0, std::placeholders::_1);
std::cout << "2^3 = " << powerOf2(3) << std::endl; // 输出: 2^3 = 8
return 0;
}
总结:可调用对象
| 项目 | Value | Value |
|---|---|---|
| 函数指针 | 函数指针 指向普通函数或静态成员函数的指针 | int (*func)(int) = &funcName; |
| 仿函数(Functors) | 重载了 operator() 的类实例,可以携带状态 | struct Foo { void operator()(); }; |
| Lambda表达式 | 定义在表达式中的匿名函数,支持捕获上下文变量 | [capture](params) { /* code */ } |
| std::function | 通用的可调用对象包装器,能够封装任何符合签名的可调用对象 | std::function<void(int)> func; |
| std::bind | 绑定函数或可调用对象的部分参数,生成新的可调用对象 | auto newFunc = std::bind(func, _1); |
1204

被折叠的 条评论
为什么被折叠?



