std::bind
是C++11引入的函数适配工具,用于绑定函数参数或调整参数顺序,生成新的可调用对象
bind(Fn&& fn, Args&&... args);
std::placeholders::_1, _2...
则相当于为新适配生成的函数对象预留一个参数进行传递想了解更多详情可以参考我的这篇博客:【C++11】 函数适配:深入理解std::bind与占位符_c++中占位符用法
代码示例如下:
#include <iostream>
#include <functional>
class Test{
public:
Test(){std::cout << "构造 Test()" << std::endl;}
~Test() {std::cout << "析构 ~Test()" << std::endl;}
};
void del(const Test *t, int num){
std::cout << num << std::endl;
delete t;
}
int main(){
Test* t = new Test();
/*bind作⽤也可以简单理解为给⼀个函数绑定好参数,然后返回⼀个参数已经设定好或者预留好的函数, 可以在合适的时候进行调用*/
/*比如:del函数,要求有两个参数,⼀个Test*, ⼀个int, 想要基于del函数,适配生成⼀个新的函数,
这个函数固定第1个参数传递t变量, 第二个参数预留出来,在调用的时候进⾏设置 */
std::function<void(int)> f = std::bind(del, t, std::placeholders::_1);
f(10);
return 0;
}
运行结果如下:
lighthouse@VM-8-10-ubuntu:bind$ ./test
构造 Test()
10
析构 ~Test()
了解了bind的作用,那么当我们在设计一些线程池,或者任务池的时候,就可以基于 bind
,将任务池中的任务设置为函数类型,函数的参数由添加任务者直接使用bind进行适配绑定设置,而任务池中的任务被处理,只需要取出一个个的函数进行执行即可。
代码如下:
#include <iostream>
#include <functional>
#include <string>
#include <vector>
// 下面代码就相当于把数组当成了线程池, 遍历每个数组就相当于从线程池中取出一个线程
void Print(const std::string& str){
std::cout << str << std::endl;
}
// 注意: 这里每个bind的类型都是 std::function<void()>
int main()
{
std::vector<std::function<void()>> task_pool;
task_pool.emplace_back(std::bind(Print, "Hello"));
task_pool.emplace_back(std::bind(Print, "World"));
task_pool.emplace_back(std::bind(Print, "I Miss You (Island1314)"));
for(auto &functor: task_pool){
// 这里的调用就相当于从线程池中取出一个线程, 然后执行
functor();
}
return 0;
}
📟 在当前的高并发服务器中,我们不得不考虑一个问题,那就是连接的超时关闭问题。我们需要避免-个连接长时间不通信,但是也不关闭,空耗资源的情况。
这时候我们就需要一个定时任务,定时的将超时过期的连接进行释放
① 创建一个定时器
#include <sys/timerfd.h>
int timerfd_create(int clockid, int flags);
clockid
flags
:0—默认阻塞属性
返回值:文件描述符 fd
② 启动定时器
int timerfd_settime(int fd, int flags,const struct itimerspec *new_value,struct itimerspec *old_value);
struct timespec {
time_t tv_sec; /* Seconds */
long tv_nsec; /* Nanoseconds */
};
struct itimerspec {
struct timespec it_interval; /* 第一次之后的超时间隔时间 */
struct timespec it_value; /* 第一次超时时间 */
};
fd
:timerfd_create
函数的返回值,文件描述符 – 创建的定时器的标识符flags
:0—相对时间,1—绝对时间;默认设置为 0 即可new
:用于设置定时器的新超时时间old
:用于接收原来的超时时间由于 Linux 下一切皆文件,定时器的操作也是和文件操作类似,而定时器定时的原理每隔一段时间(定时器的超时时间),系统就会给这个描述符对应的定时器写入一个 8 字节数据
注意:定时器在每次超时后,并不会立即单独写入一个 1
,而是累积超时次数。
read
读取定时器对应的文件描述符时,内核会返回一个 uint64_t
类型的值(8 字节),表示自上次读取后累积的超时次数。示例如下:
read
会返回 2
(表示触发了 2 次超时),而非每次超时写入read
操作案例代码如下:
#include <iostream>
#include <unistd.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/timerfd.h>
int main()
{
int timerfd = timerfd_create(CLOCK_MONOTONIC, 0);
if(timerfd < 0){
std::cerr << "timerfd_create failed" << std::endl;
return -1;
}
struct itimerspec itime;
// 设置第一次超时间隔时间为 1 s
itime.it_value.tv_sec = 1;
itime.it_value.tv_nsec = 0;
// 设置第一次超时后 每次超时时间间隔为 1 s
itime.it_interval.tv_sec = 1;
itime.it_interval.tv_nsec = 0;
// 设置定时器
int ret = timerfd_settime(timerfd, 0, &itime, NULL);
if(ret < 0){
std::cerr << "timer_settime failed" << std::endl;
return -1;
}
// 当前这个定时器描述符每隔 1 s触发可读事件
time_t start = time(NULL);
while(1){
uint64_t times;
int ret = read(timerfd, ×, 8);
if(ret < 0){
std::cerr << "read failed" << std::endl;
return -1;
}
std::cout << "当前时间: " << time(NULL) - start << std::endl;
}
close(timerfd);
return 0;
}
结果如下:
lighthouse@VM-8-10-ubuntu:timerfd$ ./timerfd
超时次数1 , 当前时间: 1
超时次数1 , 当前时间: 2
上述的例子,存在一个很大的问题,每次超时都要将所有的连接遍历一遍,如果有上万个连接,效率无疑是较为低下的。
这时候大家就会想到,我们可以针对所有的连接,根据每个连接最近一次通信的系统时间建立一个小根堆,这样只需要每次针对堆顶部分的连接逐个释放,直到没有超时的连接为止,这样也可以大大提高处理的效率。
上述方法可以实现定时任务,但是这里给大家介绍另一种方案:时间轮
同样的道理,如果我们定义了一个数组,并且有一个指针,指向数组起始位置,这个指针每秒钟向后走动一步,走到哪里,则代表哪里的任务该被执行了,那么如果我们想要定一个3s后的任务,则只需要将任务添加到 tick+3
位置,则每秒中走一步,三秒钟后 tick
走到对应位置,这时候执行对应位置的任务即可。
但是,同一时间可能会有大批量的定时任务,因此我们可以给数组对应位置下拉一个数组,这样就可以在同一个时刻上添加多个定时任务了。
当然,上述操作也有一些缺陷,比如我们如果要定义一个60s后的任务,则需要将数组的元素个数设置为60才可以,如果设置一小时后的定时任务,则需要定义3600个元素的数组,这样无疑是比较麻烦的。
一些思考:
作为一个时间轮定时器,本身并不关注任务类型,只要是时间到了就需要被执行
此时解决方案:类的析构函数 + 智能指针shared_ptr
,通过这两个技术实现定时任务延时
但是,这里我们又要考虑另一个问题,那就是假如有一个连接建立成功了,我们给这个连接设置了一个30s后的定时销毁任务,但是在第10s的时候,这个连接进行了一次通信,那么我们应该时在第30s的时候关闭,还是第40s的时候关闭呢? 应该是第40s的时候
shared_ptr
用于对 new 对象进行空间管理,当 shared_ptr
对一个对象进行管理的时候,内部有个计数器,计数为0时释放所管理对象 shared ptr
,则这时候两个任务 shared_ptr
计数为2,则第 30s 的定时任务被释放的时候,计数-1,变为1,并不为0,则并不会执行实际的析构函数,那么就相当于这个第30s的任务失效了,只有在第40s的时候,这个任务才会被真正释放。代码如下:
#include <iostream>
#include <algorithm>
#include <cstdio>
#include <vector>
#include <unordered_map>
#include <memory>
#include <thread>
#include <unistd.h>
using TaskFunc = std::function<void()>;
using ReleaserFunc = std::function<void()>;
// 定时器任务
class TimerTask{
public:
TimerTask(uint64_t id, uint32_t delay, const TaskFunc& cb, int start_tick = 0):
_id(id),
_timeout(delay),
_cb(cb),
_start_tick(start_tick),
_canceled(false)
{}
~TimerTask() {
if (!_canceled) {
std::thread(_cb).detach(); // 异步执行
}
// if (!_canceled) _cb(); // 用这个的话 _cb 就会先执行, 否则后执行
_release();
}
void SetReleaser(const ReleaserFunc& release){_release = release;}
ReleaserFunc GetReleaser() const{return _release;}
uint64_t GetId() const {return _id;}
void Cancel() {_canceled = true;} // 取消定时任务
uint32_t GetDelayTime() const { return _timeout; }
int GetStartTick() const { return _start_tick; }
void SetStartTick(int tick) { _start_tick = tick; } // 设置定时器任务的起始 tick
private:
int _start_tick; // 记录定时器任务的起始 tick
uint64_t _id; // 定时器任务 id
uint32_t _timeout; // 定时任务超时时间
bool _canceled; // 是否取消定时任务(true: 取消)
TaskFunc _cb; // 定时器对象要执行的定时任务
ReleaserFunc _release; // 用于删除 TimerWheel 中保存的定时器对象信息
};
// 定时器轮
class TimerWheel{
private:
using WeakTask = std::weak_ptr<TimerTask>; // 辅助 shared_ptr 解决循环引用问题
using PtrTask = std::shared_ptr<TimerTask>;
public:
TimerWheel(int capacity = 60):
_capacity(capacity),
_tick(0)
{
_wheel.resize(_capacity);
}
// 1. 添加定时任务
void AddTimer(uint64_t id, uint32_t delay, const TaskFunc& cb){
if (delay > _capacity) {
std::cerr << "Timer delay exceeds capacity" << std::endl;
return;
}
PtrTask pt(new TimerTask(id, delay, cb, _tick));
pt->SetReleaser(std::bind(&TimerWheel::RemoveTimer, this, id));
int pos = (_tick + delay) % _capacity; // 计算定时器任务在轮子上的位置
_wheel[pos].push_back(pt); // 将定时器任务添加到轮子上
_timers[id] = WeakTask(pt); // 不能用 shared_ptr, 否则永久有shared_ptr 来管理, 计数不归0
}
// 2. 删除定时任务
void RemoveTimer(uint64_t id){
auto it = _timers.find(id);
if(it == _timers.end()) return ;
_timers.erase(it);
}
// 3. 执行定时任务
void RunTimerTask(){
_tick = (_tick + 1) % _capacity;
_wheel[_tick].clear(); // 清空当前 tick 的任务
}
// 4. 刷新/延迟 定时任务
void RefreshTimer(uint64_t id) {
// 1, 通过保存定时器对象的 weak_ptr 来获取定时器对象 添加到轮子中
auto it = _timers.find(id);
if (it == _timers.end()) // 没找到定时任务, 无法刷新和延迟
return;
PtrTask pt = it->second.lock(); // lock获取weak_ptr管理的对象对应的shared_ptr
if (!pt) return;
// 方法1:下面两行处理的刷新之后定时时间也重新计时
int remaining = pt->GetDelayTime();
// 由于上面这里的刷新把延迟时间也会重新计算, 因此任务原定5秒后执行,已过去3秒,刷新后仍按5秒计算,导致实际延迟8秒
// 解决办法: 记录起始 tick
// // 方法2:刷新不延迟计时
// // 计算剩余时间 = 原始延迟 - (当前 tick - 起始 tick)
// int elapsed = (_tick - pt->GetStartTick() + _capacity) % _capacity; // 防止负数
// int remaining = pt->GetDelayTime() - elapsed;
// remaining = std::max(remaining, 1); // 确保至少延迟1秒
// // 更新任务的起始 tick 为当前 tick
// pt->SetStartTick(_tick);
int pos = (_tick + remaining) % _capacity;
_wheel[pos].push_back(pt);
}
// 5. 取消定时任务
void CancelTimer(uint64_t id) {
auto it = _timers.find(id);
if(it == _timers.end()) return ;
PtrTask pt = it->second.lock(); // lock获取weak_ptr管理的对象对应的shared_ptr
// 如果weak_ptr管理的对象已经被销毁(即引用计数为0),lock()会返回一个空的shared_ptr
// 如果对象仍然存在,lock()会返回一个有效的shared_ptr,指向该对象
if(pt) pt->Cancel(); // 取消定时任务
}
private:
int _tick; // 当前指针, 走到哪释放哪(相当于执行哪里任务)
int _capacity; // 定时器轮的大小 -- 最大延迟时间
std::vector<std::vector<PtrTask>> _wheel; // 定时器轮
std::unordered_map<uint64_t, WeakTask> _timers; // 定时器任务 id 和定时器对象的映射关系
};
struct Test{
Test(){std::cout << "构造" << std::endl;}
~Test(){std::cout << "析构" << std::endl;}
};
void DelTest(Test *t){
std::cout << "删除" << std::endl;
delete t;
t = nullptr;
}
int main() {
TimerWheel tw(10); // 假设时间轮容量为10
// 添加一个5秒后执行的任务 -- 保证刷新任务不影响执行
tw.AddTimer(1, 3, [](){
std::cout << "任务执行时间:" << time(nullptr) << std::endl;
});
// 模拟时间推进(单位:秒)
for (int i = 0; i < 8; ++i) {
std::cout << "当前时间:" << time(nullptr) << " tick: " << i << std::endl;
// 在第2秒时刷新任务
if (i == 2) {
std::cout << "刷新任务" << std::endl;
tw.RefreshTimer(1);
}
sleep(1);
tw.RunTimerTask();
}
std::cout << "------------------" << std::endl;
Test *t = new Test();
tw.AddTimer(2,2, std::bind(DelTest, t));
for(int i = 0; i < 4; i++){
std::cout << "tick: " << i << " , 刷新定时器任务" << std::endl;
sleep(1);
if(i == 1){
tw.CancelTimer(2); // 取消任务
}
tw.RefreshTimer(2); // 刷新定时器任务1
tw.RunTimerTask(); // 向后移动指针
}
return 0;
}
结果如下:
lighthouse@VM-8-10-ubuntu:timewheel$ ./timewheel
当前时间:1745495736 tick: 0
当前时间:1745495737 tick: 1
当前时间:1745495738 tick: 2
刷新任务
当前时间:1745495739 tick: 3
当前时间:1745495740 tick: 4
当前时间:1745495741 tick: 5
任务执行时间:1745495741
当前时间:1745495742 tick: 6
当前时间:1745495743 tick: 7
------------------
构造
tick: 0 , 刷新定时器任务
tick: 1 , 刷新定时器任务
tick: 2 , 刷新定时器任务
tick: 3 , 刷新定时器任务
tick: 4 , 刷新定时器任务
正则表达式(regular expression
)描述了一种字符串匹配的模式(pattern
),可以用来检查一个串是否含有某种子串、将匹配的子串替换或者从某个串中取出符合某个条件的子串等
bool std::regex_match(const std::string& src, std::mismatch &matches, std::regex &e)
src: 原始字符串
matches: 正则表达式可以从原始字符串中匹配并提取符合某种规则的数据, 提取数据就放在 mathces 中, 是一个类似于数组的容器
e: 正则表达式的匹配规则
返回值: 用于确定匹配是否成功
在 C++ 中,std::smatch
是 <regex>
标准库中用于存储正则表达式匹配结果 的类模板。它专门用于处理 std::string
类型的字符串匹配,并能够保存完整的匹配结果以及每个子表达式的匹配内容。
std::smatch
是 std::match_results<std::string::const_iterator>
的别名。
功能
bool
类型转换)str()
)[i]
)position(i)
)和长度(length(i)
)代码示例
#include <iostream>
#include <regex>
#include <string>
int main() {
std::string text = "Contact us at support@example.com";
std::regex pattern(R"((\w+)@(\w+\.\w+))"); // 匹配 邮箱 分组: 用户名 + 域名
std::smatch match;
if (std::regex_search(text, match, pattern)) {
std::cout << "完整匹配: " << match.str() << std::endl;
std::cout << "用户名: " << match[1].str() << std::endl;
std::cout << "域名: " << match[2].str() << std::endl;
}
else {
std::cout << "未找到匹配。" << std::endl;
}
return 0;
}
// 输出
完整匹配: support@example.com
用户名: support
域名: example.com
方法/属性 | 说明 |
---|---|
match.size() | 返回匹配的子表达式数量(包括整个匹配) |
match[i] | 获取第 i 个子表达式的匹配结果(std::sub_match) |
match.str(i) | 获取第 i 个子表达式的匹配字符串 |
match.position(i) | 获取第 i 个子表达式在原字符串中的起始位置 |
match.length(i) | 获取第 i 个子表达式的匹配长度 |
match.empty() | 判断是否匹配成功(返回false表示匹配成功) |
#include <iostream>
#include <regex>
#include <string>
int main() {
std::string url = "https://www.example.com/path/to/page.html ";
std::regex pattern(R"(^(https?|ftp):\/\/([^\/\?:]+))"); // 匹配协议和域名
std::smatch match;
if (std::regex_search(url, match, pattern)) {
std::cout << "协议: " << match[1] << std::endl;
std::cout << "域名: " << match[2] << std::endl;
} else {
std::cout << "URL 格式不正确。" << std::endl;
}
return 0;
}
// 输出
协议: https
域名: www.example.com
#include <iostream>
#include <string>
#include <regex>
int main()
{
// HTTP 请求行格式: GET /path HTTP/1.1\r\n
std::string str = "GET /Island/login?user=xiao&pass=123456 HTTP/1.1\r\n";
std::smatch matches;
// 请求方法匹配: GET|POST|PUT|DELETE|HEAD|OPTIONS
std::regex method_regex("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?");
// GET|HEAD|POST|PUT|DELETE 表示匹配并提取其中任意一个字符串
// [^?]* [^?]匹配非问号字符, 后边的*表示0次或多次
// \\?(.*) \\? 表示原始的?字符 (.*)表示提取?之后的任意字符0次或多次,知道遇到空格
// HTTP/1\\.[01] 表示匹配以HTTP/1.开始,后边有个0或1的字符串
// (?:\n|\r\n)? (?: ...) 表示匹配某个格式字符串,但是不提取, 最后的?表示的是匹配前边的表达式0次或1次
bool ret = std::regex_match(str, matches, method_regex);
if(!ret) return -1;
std::cout << "完整匹配: " << matches[0] << std::endl;
std::cout << "HTTP 方法: " << matches[1] << std::endl;
std::cout << "路径: " << matches[2] << std::endl;
std::cout << "查询参数: " << (matches[3].matched ? matches[3].str() : "") << std::endl;
std::cout << "协议版本: " << matches[4] << std::endl;
return 0;
}
输出如下:
完整匹配: GET /Island/login?user=xiao&pass=123456 HTTP/1.1
HTTP 方法: GET
路径: /Island/login
查询参数: user=xiao&pass=123456
协议版本: HTTP/1.1
🧠 ① 正则表达式逻辑解析
std::regex method_regex("(GET|HEAD|POST|PUT|DELETE) ([^?]*)(?:\\?(.*))? (HTTP/1\\.[01])(?:\n|\r\n)?");
🔍 正则表达式各部分含义:
正则片段 | 匹配内容 |
---|---|
(GET | HEAD |
([^?]*) | 捕获路径(不包含问号?的部分) |
(?:\\?(.*))? | 可选捕获查询字符串(?后的内容) |
(HTTP/1\\.[01]) | 捕获协议版本(HTTP/1.0 或 HTTP/1.1) |
(?:\n | \r\n)? |
📌 ② 测试字符串
std::string str = "GET /Island/login?user=xiao&pass=123456 HTTP/1.1\r\n";
🧪 分解匹配过程:
匹配项 | 内容 |
---|---|
matches[0] | 整体匹配:GET /Island/login?user=xiao&pass=123456 HTTP/1.1 |
matches[1] | 方法:GET |
matches[2] | 路径:/Island/login |
matches[3] | 查询参数:user=xiao&pass=123456 |
matches[4] | 协议版本:HTTP/1.1 |
完善
std::regex method_regex(R"((GET|HEAD|POST|PUT|DELETE) ([^? ]+)(?:\?([^ ]+))? (HTTP/1\.[01])\r?\n)");
特性 | 第一个 | 第二个 | 说明 |
---|---|---|---|
原始字符串 | ❌ 普通字符串 | ✅ 使用R"()"原始字符串 | R"()"避免双重转义,提高可读性 |
路径匹配 | ([^?]*) | ([^? ]+) | 第二个明确禁止空格,更符合 HTTP 请求行规范 |
查询参数匹配 | (?:\\?(.*))? | (?:\?([^ ]+))? | 第二个限制查询参数不包含空格,更安全 |
协议版本匹配 | (HTTP/1\.[01]) | (HTTP/1\.[01]) | 相同 |
换行符匹配 | (?:\n | \r\n)? | \r?\n |
🚨 关键问题:为何第一个正则表达式可能失败?
1)未正确处理换行符
std::regex_match
要求完全匹配整个字符串 。(?:\n|\r\n)?
是可选的,但实际输入包含 \r\n
,可能导致匹配失败。2)路径匹配不严谨
([^?]*)
允许路径中包含空格(如 /path with space
),但 HTTP 请求行中路径不允许空格 (空格是方法、路径、协议的分隔符)。3)查询参数匹配贪婪
(?:\\?(.*))?
中 .*
是贪婪匹配,可能导致匹配到协议版本前的内容。🏭 每一个 Connection
对连接进行管理,最终都不可避免需要涉及到应用层协议的处理,因此在 Connection
中需要设置协议处理的上下文来控制处理节奏。但是应用层协议千千万,为了降低合度,这个协议接收解析上下文就不能有明显的协议倾向,它可以是任意协议的上下文信息,因此就需要一个通用的类型来保存各种不同的数据结构
在C语言中,通用类型可以使用
void*
来管理,但是在C++中,boost库 和 C++17 给我们提供了一个通用类型any来灵活使用,如果考虑增加代码的移植性,尽量减少第三方库的依赖,则可以使用C++17特性中的any,或者自己来实现。而这个any通用类型类的实现其实并不复杂,以下是简单的部分实现。
结论:必须拥有一个容器能够保存各种不同的类型结构数据
通用类型:any
设计实现一个 any 类
① 一个容器,容器中可以保存各种不同类型的数据
解决方案一:模板
template<class T>
class Any {
private:
T _content;
};
Any<int> a;
,此时就需要传类型作为参数模板,也就是说使用的时候要确定其类型Any a; a = 10; a = "abc"...
解决方案二:嵌套一个类,专门用于保存其他类型的数据,而 Any 类保存的是固定类的对象
class Any {
private:
class holder {
// ...
};
template<class T>
class placeholder: holder {
T _val;
};
holder* _content;
};
placehoder
,让 holder
继承于 placeholder
,而Any类保存父类指针即可std::any
是 C++17 引入的标准库类型,用于存储任意类型的单个值(类型安全的“通用容器”)。它支持动态类型存储和访问,允许在运行时更改所存储的值类型,同时保证类型安全性
(1)动态类型存储 可以存储任何类型的值,但同一时间只能保存一种类型。
std::any a = 42; // 存储 int
a = std::string("Hello"); // 替换为 string
(2)类型安全访问
通过 std::any_cast<T>
安全地访问存储的值:
T*
,若类型不匹配返回 nullptr
std::bad_any_cast
异常(需捕获)if (auto* p = std::any_cast<int>(&a)) {
std::cout << *p << "\n";
} else {
std::cout << "类型不匹配\n";
}
(3)拷贝与移动语义 支持拷贝构造、赋值和移动操作,但要求存储的类型满足相应操作(如可拷贝/移动)。
std::any a = 10;
std::any b = a; // 拷贝构造
std::any c = std::move(a); // 移动构造(a 变为空)
(4)空状态(Empty State)
默认构造的 std::any
为空,调用 has_value()
返回 false
。
std::any a;
if (!a.has_value()) {
std::cout << "a 为空\n";
}
<font color = #f9906f>#include <iostream>
#include <any>
int main() {
std::any a = 3.14; // 存储 double
std::cout << *std::any_cast<double>(&a) << "\n"; // 输出 3.14
a = true; // 替换为 bool
std::cout << std::boolalpha << *std::any_cast<bool>(&a) << "\n"; // 输出 true
a.reset(); // 清空, 会触发析构
if (!a.has_value()) {
std::cout << "a 已清空\n";
}
try {
auto val = std::any_cast<int>(a); // 空 any 转换抛出异常
} catch (const std::bad_any_cast& e) {
std::cout << "错误: " << e.what() << "\n"; // 输出错误信息
}
return 0;
}
std::any
内部可能使用堆分配存储(小对象优化可能实现),频繁使用可能导致内存碎片或性能瓶颈。std::any
可能导致代码难以维护。std::variant
的区别 std::variant
:类型受限(编译时指定可选类型),无动态分配,适合有限类型集合。std::any
:类型无限制,运行时动态确定,适合通用性场景。小结:std::any
提供了灵活的类型擦除能力,但应权衡其性能与安全性。在需要动态类型处理时(如脚本绑定、配置管理),它是理想选择;但在高性能或类型明确的场景中,优先使用 std::variant
或模板泛型。
class Any{
private:
class holder{
public:
virtual ~holder() = default;
virtual const std::type_info& type() const = 0;
virtual holder* clone() const = 0;
};
template<class T>
class placeholder: public holder{
public:
placeholder(const T& val): _val(val) {}
virtual const std::type_info& type() const {return typeid(T);} // 获取子类对象保存的数据类型
virtual holder* clone() const {return new placeholder(_val);} // 针对当前的对象克隆出一个新的子类对象
public:
T _val;
};
holder* _content; // 指向holder的指针
public:
Any():_content(nullptr) {}
template<class T>
Any(const T& val):_content(new placeholder<T>(val)){} // 模板构造函数
Any(const Any& other):_content(other._content ? other._content->clone(): nullptr){} // 拷贝构造
~Any(){delete _content;}
Any &swap(Any &other){
std::swap(_content, other._content); // 交换两个指针
return *this; // 返回当前对象的引用
}
template<class T>
T *get() const { // 返回子类对象保存数据的指针
// 保证获取的数据类型和保存的数据类型一致
// if(typeid(T) != _content->type()) // 如果类型不匹配
// return nullptr;
assert(typeid(T) == _content->type()); // 断言类型匹配 --> 不匹配直接退出
return &((placeholder<T>*)_content)->_val; // 类型转化->成员->取地址
}
template<class T>
Any& operator=(const T&val){ // 模板赋值运算符的重载
// 先构造一个临时对象,然后交换
Any(val).swap(*this);
return *this;
}
Any& operator=(const Any& other){
Any(other).swap(*this);
return *this;
}
};
测试如下:
// 测试有无内存泄漏
struct Test{
Test(){std::cout << "构造" << std::endl;}
Test(const Test&other){std::cout << "拷贝构造" << std::endl;}
~Test(){std::cout << "析构" << std::endl;}
};
void func(){
// 系统自带
std::any a;
a = 10;
int *pi = std::any_cast<int>(&a);
std::cout << "int a = " << *pi << std::endl;
a = std::string("hello");
std::string *ps = std::any_cast<std::string>(&a);
std::cout << "string a = " << *ps << std::endl;
}
int main()
{
Any a;
a = 10;
int *pa = a.get<int>(); // 获取int类型的指针
std::cout << "int a = " << *pa << std::endl;
a = std::string("hello");
std::string *ps = a.get<std::string>();
std::cout << "string a = " << *ps << std::endl;
// 两次析构对象: 相当于在 placeholder 中保存了一个对象的引用
// 先析构原来的对象,再析构临时对象
{
Test t;
a = t;
}
std::cout << "------------" << std::endl;
func(); // 测试系统自带的any类
return 0;
}
// 结果如下:
int a = 10
string a = hello
构造
拷贝构造
析构
------------
int a = 10
string a = hello
析构