【高并发服务器】四、通用类型容器any
文章目录
- 1、为何需要通用类型
- 2、`c++17`中的`any`类
- 3、自主实现`any`类
- 设计思想
- 主体框架
- 函数实现

1、为何需要通用类型
每一个 Connection
对连接进行管理,最终都不可避免需要涉及到应用层协议的处理,因此在 Connection
中需要设置协议处理的上下文来控制处理节奏。
但是应用层协议千千万,为了降低耦合度,这个协议接收解析上下文就不能有明显的协议倾向,它可以是任意协议的上下文信息,因此就需要一个通用的类型来保存各种不同的数据结构。
在 C
语言中,通用类型可以使用 void*
来管理,但是在 C++
中,boost
库和 C++17
给我们提供了一个通用类型 any
来灵活使用,如果考虑增加代码的移植性,尽量减少第三方库的依赖,则可以使用 C++17
特性中的 any
,或者我们自己来实现。而这个 any
通用类型类的实现其实并不复杂。
下面我们尝试着来实现一下,主要是了解其思想,这样也就避免了第三方库的使用了,如果不想看这部分的话,可以直接看后面 std
中的 any
类使用,都是一样的!
2、c++17
中的any
类
any类的官方文档
要注意的是,因为 any
是属于 c++17
的,所以在编译的时候 需要添加 -std=c++17
选项才能编译通过!
下面我们来看看库中的 any
是如何使用的:
#include <any>
#include <iostream>int main()
{// T* any_cast<class T>() 成员函数用于返回any对象值的地址std::any a = 1;std::cout << a.type().name() << ": " << std::any_cast<int>(a) << std::endl;a = 3.14;std::cout << a.type().name() << ": " << std::any_cast<double>(a) << std::endl;a = true;std::cout << std::boolalpha << a.type().name() << ": " << std::any_cast<bool>(a) << std::endl;// 有误的转型try{a = 1;std::cout << std::any_cast<float>(a) << std::endl;}catch (const std::bad_any_cast& e){std::cout << e.what() << '\n';}// 拥有值a = 1;if(a.has_value())std::cout << a.type().name() << std::endl;// 重置a.reset();if (!a.has_value())std::cout << "no value" << std::endl;// 指向所含数据的指针,对变量取地址使用a = 1;int* i = std::any_cast<int>(&a);std::cout << *i << std::endl;return 0;
}// 执行结果:
[liren@VM-8-7-centos test]$ g++ -o any any.cpp -std=c++17
[liren@VM-8-7-centos test]$ ./any
i: 1
d: 3.14
b: true
bad any_cast
i
no value
1
3、自主实现any
类
设计思想
首先我们想,既然要实现一个通用类型的类,首先想到的方案就是使用模板,如下所示:
template <class T>
class Any
{
private:T _content;
};
看起来好像就是通用的,但是使用起来却是这样子的:实例化的时候必须传入模板参数,也就是传入类型,那这和不使用模板也没啥区别呀:
Any<int> a;
Any<double> b;
这并不是我们想要的效果,我们想要的效果是这样子的:
Any a;
a = 10; // 赋值为整形类型
a = "abcd"; // 赋值为字符串类型
……
所以单单使用模板是解决不了问题的,我们需要换个方案!
新的方案:我们可以在 Any
类中再设计一个类,专门用于保存其它类型的数据,而 Any
类保存的是固定类的对象,如下所示:
class Any
{
private:template <class T>class placeholder // 用来存放其它数据类型的类{T _val;};placeholder* _content; // 上面类的指针
};
此时好像并没有什么变化,还是需要在实例化 Any
的时候传入具体的类型,所以我们还得转变思路,引入多态的思想!
我们可以给 placeholder
类设计一个父类 holder
,让 Any
类保存 holder
的指针,当 Any
容器需要保存一个数据的时候,只需要通过 placeholder
子类实例化一个特定类型的子类对象出来,让子类对象保存数据!
而当需要操作数据的时候,我们通过传入 placeholder
对象给 Any
,本质就是 holder
的指针拿到了 placeholder
,就形成了一种多态的机制来操控 placeholder
类!
class Any
{
private:class holder {... };template <class T>class placeholder : holder // 用来存放其它数据类型的类{T _val;};holder* _content; // 父类的指针
};
主体框架
结合下面的代码,我们可以看到思路就是实例化一个 Any
类的时候不需要传入一个特定类型参数,因为它会被内部的 _content
指针所拿到,拿到之后其实就与 placeholder
类形成了多态,此时操作 _content
指针就相当于控制 placeholder
类,然后 Any
对象的操作就可以调用 placeholder
类的接口完成操作!
class Any
{
private:class holder{public:virtual ~holder() {} // 析构函数,父类需要设为虚函数才能正确释放子类virtual std::type_info type() = 0; virtual holder* clone() = 0; };template <class T>class placeholder : holder{public:placeholder(const T& val) {} // 构造函数virtual std::type_info type() = 0; // 用于返回子类中持有的数据类型virtual holder* clone() = 0; // 用于拷贝生成新的holder对象T _val; // 任意类型的数据};holder* _content; // holder类对象,通过多态方式来操作placeholder对象
public:Any();~Any();template <class T>Any(const T& val); // 任意类型数据的构造函数Any(const Any& other); // Any对象类型的构造函数template <class T>Any& operator=(const T& val); // 任意类型数据的赋值重载函数Any& operator=(const Any& other); // Any对象类型的赋值重载函数template <class T>T* get(); // 返回placeholder对象保存的数据的指针
};
函数实现
从上面的主体框架可以看出来,其实要实现的大部分接口都是构造函数等等,并不复杂,要注意的就是 c++
一些语法特性罢了!
#include <iostream>
#include <typeinfo>
#include <string>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 const std::type_info& type() { return typeid(T); }// 针对当前的对象自身,克隆出一个新的子类对象virtual holder* clone() { return new placeholder<T>(_val); } T _val; // 任意类型的数据};holder* _content; // holder类对象,通过多态方式来操作placeholder对象
public:Any() : _content(nullptr) {}~Any() { delete _content; }// 任意类型数据的构造函数template <class T>Any(const T& val) : _content(new placeholder<T>(val)) {} // Any类型的构造函数Any(const Any& other) { if(other._content == nullptr)_content = nullptr;else_content = other._content->clone();}// 任意类型数据的赋值重载函数template <class T>Any& operator=(const T& val){// 为val构造一个临时的通用容器,然后与当前容器自身进行指针交换,临时对象释放的时候,原先保存的数据也就被释放Any(val).swap(*this);return *this;}// Any类型的赋值重载函数Any& operator=(const Any& other){Any(other).swap(*this);return *this;}// 返回placeholder对象保存的数据的指针template <class T>T* get(){if(_content->type() != typeid(T))return nullptr;return &((placeholder<T>*)_content)->_val;}const std::type_info& type() { return _content->type(); }
private:Any& swap(Any& other){std::swap(_content, other._content);return *this;}
};class Test
{
public:Test() {std::cout << "构造" << std::endl;}Test(const Test &t) {std::cout << "拷贝" << std::endl;}~Test() {std::cout << "析构" << std::endl;}
};int main()
{Any a;a = 10;int* pa = a.get<int>();std::cout << *pa << std::endl;std::cout << a.type().name() << std::endl;a = std::string("lirendada");std::string* sa = a.get<std::string>();std::cout << *sa << std::endl;std::cout << a.type().name() << std::endl;a = Test();Test* ta = a.get<Test>();std::cout << a.type().name() << std::endl;return 0;
}// 执行结果:
[liren@VM-8-7-centos test]$ g++ -o any any.cpp -std=c++11
[liren@VM-8-7-centos test]$ ./any
10
lirendada
构造
拷贝
析构
析构