服务器设计细节 之 【通用类型any】(自定义实现Any、C++17any简介、typeid\type_info)
服务器支持的协议可能会不断扩展保存各种不同类型的结构数据综上,我们需要一个,来实现这一目标。
·
目录
- 每个网络连接都需要拥有一个用于请求接收与解析的上下文
- 上下文的类型或结构不能固定,因为服务器支持的协议可能会不断扩展。不同协议往往需要各自不同的上下文结构
- 因此,必须有一种容器,能够保存各种不同类型的结构数据
综上,我们需要一个通用类型 Any,来实现这一目标
1.通用类型设计思想
template <class T>
class Any {
private:
T _content;
};
1.1.初始思路:直接使用模板,但这种方式的问题在于:实例化时必须明确指定类型
Any<int> a; // 只能保存 int
而我们真正想要的是:
Any a;
a = 10; // 保存 int
a = "abc"; // 保存字符串
1.2.类型擦除 + 继承
嵌套设计一个基类 holder,再通过模板子类 placeholder 保存具体类型的数据,Any 类中只保存基类指针
class Any {
private:
class holder {
// 基类,用于类型擦除
};
template <class T>
class placeholder : public holder {
public:
T _val;
placeholder(const T& val) : _val(val) {}
};
holder* _content; // 指向具体类型的 placeholder
};
当 Any 需要保存某个数据时,只需实例化对应的 placeholder<T> 子类对象,让该子类对象实际持有数据,而 Any 通过基类指针来统一管理
1.3.简易实现
class Any
{
private:
//父类
class holder
{
public:
virtual ~holder(){}
virtual const std::type_info& type() = 0;
virtual holder* clone() = 0;
};
//子类
template<class T>
class placeholder : public holder
{
public:
//传值构造
placeholder(const T& val) :_val(val){}
//析构可以不写,成员自动析构即可
// virtual ~holder(){}
//获取子类对象保存的数据类型
virtual const std::type_info& type() { return typeid(T); };
//针对当前的自身对象,克隆出一个新的子类对象
virtual holder* clone() { return new placeholder<T>(_val); }
public:
T _val;
};
holder* _content;
public:
Any() :_content(nullptr){}
template<class T>
Any(const T& val) :_content(new placeholder<T>(val)){}
//为什么要单独写 //只能这样调用,palceholder不是any的成员,只能通过指针
Any(const Any& other) :_content(other._content ? other._content->clone() : nullptr){}
~Any() { delete _content; }
//返回子类对象保存的数据的指针
template<class T>
T* get()
{
//返回类型要一致,不能隐式类型转换等
assert(typeid(T) == _content->type());
return &(static_cast<placeholder<T>*>(_content)->_val);
}
//交换,支持连续的交换
Any& MySwap(Any& other)
{
std::swap(_content, other._content);
return *this;
}
template<class T>
Any& operator=(const T& val)
{
//临时对象,自动析构
Any(val).MySwap(*this);
return *this;
}
Any& operator=(const Any& other)
{
Any(other).MySwap(*this);
return *this;
}
};
(1)模板构造函数
template<class T>
Any(const T& val) : _content(new placeholder<T>(val)) {}
- 根据 val 的类型 T,实例化 placeholder<T>
- 用 val 初始化 placeholder 的 _val 成员
- 将 _content 指向这个新创建的对象
(2)拷贝构造函数 - 深拷贝
Any(const Any& other)
: _content(other._content ? other._content->clone() : nullptr) {}
如果只是拷贝指针,析构时 double free!程序崩溃
(3)operator=(const T& val)
template<class T>
Any& operator=(const T& val)
{
Any(val).MySwap(*this); // 关键!
return *this;
}
- Any(val) 创建临时对象
- 临时对象与 *this 交换内容
- 临时对象析构,释放原内容
(4)delete _content 能正确释放
~Any() { delete _content; }
- holder 有虚析构函数(virtual ~holder())
- delete _content 会正确调用 placeholder<int> 的析构函数
- 然后释放 placeholder 对象占用的内存
1.4.typeid
#include <typeinfo>
typeid(类型) // 编译时确定
typeid(表达式) // 可能运行时确定(多态类型)
typeid 是 C++ 的关键字,用于在运行时获取类型信息,返回 const std::type_info&
按参数类型分类
| 参数类型 | 求值行为 | 返回值 | 示例 |
|---|---|---|---|
| 类型名 | 编译时 | 该类型的 type_info |
typeid(int) |
| 非多态类型表达式 | 编译时(不求值) | 表达式静态类型的 type_info |
typeid(10 + 20) |
| 多态类型表达式 | 运行时(求值) | 对象动态类型的 type_info |
typeid(*ptr) |
| 多态类型空指针 | 运行时(抛出异常) | 抛出 std::bad_typeid |
typeid(*nullptr) |
多态 vs 非多态行为对比
| 类型分类 | 条件 | typeid(指针) |
typeid(*指针) |
|---|---|---|---|
| 非多态 | 无虚函数 | 指针静态类型 | 指针静态类型(不识别派生类) |
| 多态 | 有虚函数 | 指针静态类型 | 指向对象的动态类型(识别派生类) |
1.5.type_info
std::type_info 是 C++ 运行时类型信息 (RTTI)系统的核心类,用于存储和比较类型信息
公开成员函数
| 方法 | 功能 | 返回值 | 示例 |
|---|---|---|---|
bool operator==(const type_info& rhs) const noexcept |
比较两个类型是否相同 | 相同返回 true |
typeid(int) == typeid(int) |
bool operator!=(const type_info& rhs) const noexcept |
比较两个类型是否不同 | 不同返回 true |
typeid(int) != typeid(double) |
bool before(const type_info& rhs) const noexcept |
检查当前类型是否排在 rhs 之前(实现定义的排序) | 是返回 true |
typeid(int).before(typeid(double)) |
const char* name() const noexcept |
返回类型的实现定义名称 | 以空字符结尾的字符串 | cout << typeid(int).name() |
size_t hash_code() const noexcept (C++11) |
返回类型的哈希值 | 哈希值(可作 unordered_map 的键) | size_t h = typeid(int).hash_code() |
特点
| 特性 | 说明 |
|---|---|
| 不可拷贝 | 拷贝构造函数和赋值运算符被删除 |
| 不可取地址 | 指向 type_info 的指针可能不稳定 |
| 生命周期 | 程序整个生命周期内有效 |
| 唯一性 | 同一类型在整个程序中有唯一的 type_info 对象 |
限制和注意事项
| 限制 | 说明 | 示例 |
|---|---|---|
| 不可拷贝/赋值 | type_info 对象不可拷贝或赋值 | type_info copy = typeid(int); // wrong |
| 仅存储指针/引用 | 通常存储指针或引用 | const type_info& ref = typeid(int); |
| 多态要求 | 对指针解引用需要虚函数才能获得动态类型 | typeid(*ptr) 需要基类有虚函数 |
| 实现定义名称 | name() 返回值因编译器而异 |
需要平台特定解码 |
| before() 顺序 | 严格弱排序,但排序规则是实现定义的 | 可用于 map 但不能假设特定顺序 |
| 静态类型 | 非多态类型返回静态类型 | typeid(ptr) 返回指针类型 |
2.C++17 Any
2.1.构造函数与析构函数
| 构造函数 | 描述 | 示例 |
|---|---|---|
any() noexcept |
默认构造一个空对象 | std::any a; |
any(const any& other) |
拷贝构造函数(深拷贝) | std::any a2(a1); |
any(any&& other) noexcept |
移动构造函数 | std::any a2(std::move(a1)); |
template<class T> any(T&& value) |
类型转换构造(完美转发) | std::any a = 42; |
~any() |
析构函数,释放资源 | 自动调用 |
2.2. 赋值操作符
| 操作符 | 描述 | 示例 |
|---|---|---|
any& operator=(const any& other) |
拷贝赋值 | a2 = a1; |
any& operator=(any&& other) noexcept |
移动赋值 | a2 = std::move(a1); |
template<class T> any& operator=(T&& value) |
从值赋值(完美转发) | a = 42; |
2.3.修改操作
| 方法 | 描述 | 示例 |
|---|---|---|
void reset() noexcept |
销毁所含对象,变为空 | a.reset(); |
void swap(any& other) noexcept |
交换两个 any 对象的内容 | a1.swap(a2); |
2.4.观察操作
| 方法 | 描述 | 示例 |
|---|---|---|
bool has_value() const noexcept |
检查是否包含值 | if (a.has_value()) |
const std::type_info& type() const noexcept |
获取存储类型的 type_info | if (a.type() == typeid(int)) |
2.5. 非成员函数
| 函数 | 描述 | 示例 |
|---|---|---|
void swap(any& a, any& b) noexcept |
交换两个 any 对象 | std::swap(a1, a2); |
template<class T> T any_cast(const any& operand) |
值转换(拷贝),失败抛出 std::bad_any_cast |
int val = std::any_cast<int>(a); |
template<class T> T* any_cast(any* operand) noexcept |
指针转换,失败返回 nullptr | int* p = std::any_cast<int>(&a); |
template<class T> T any_cast(any&& operand) |
移动转换 | int val = std::any_cast<int>(std::move(a)); |
template<class T> T* any_cast(const any* operand) noexcept |
const 指针转换 | const int* p = std::any_cast<int>(&a); |
2.6.使用建议
- 优先使用指针版本的 any_cast:避免异常开销
- 避免频繁拷贝大对象:使用移动语义或指针
- 类型检查后再访问:使用 type() 或 has_value()
- 重置空对象:使用 reset() 释放资源
openEuler 是由开放原子开源基金会孵化的全场景开源操作系统项目,面向数字基础设施四大核心场景(服务器、云计算、边缘计算、嵌入式),全面支持 ARM、x86、RISC-V、loongArch、PowerPC、SW-64 等多样性计算架构
更多推荐
所有评论(0)