【高并发服务器】四、通用类型容器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 是如何使用的:

cpp 复制代码
#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

设计思想

​ 首先我们想,既然要实现一个通用类型的类,首先想到的方案就是使用模板,如下所示:

cpp 复制代码
template <class T>
class Any
{
private:
    T _content;
};

​ 看起来好像就是通用的,但是使用起来却是这样子的:实例化的时候必须传入模板参数,也就是传入类型,那这和不使用模板也没啥区别呀:

cpp 复制代码
Any<int> a;
Any<double> b;

​ 这并不是我们想要的效果,我们想要的效果是这样子的:

cpp 复制代码
Any a;
a = 10;		// 赋值为整形类型
a = "abcd"; // 赋值为字符串类型
......

​ 所以单单使用模板是解决不了问题的,我们需要换个方案!

​ 新的方案:我们可以在 Any 类中再设计一个类,专门用于保存其它类型的数据,而 Any 类保存的是固定类的对象,如下所示:

cpp 复制代码
class Any
{
private:
    template <class T>
    class placeholder // 用来存放其它数据类型的类
    {
        T _val;
    };
    
    placeholder* _content; // 上面类的指针
};

​ 此时好像并没有什么变化,还是需要在实例化 Any 的时候传入具体的类型,所以我们还得转变思路,引入多态的思想

​ 我们可以给 placeholder 类设计一个父类 holder ,让 Any 类保存 holder 的指针,当 Any 容器需要保存一个数据的时候,只需要通过 placeholder 子类实例化一个特定类型的子类对象出来,让子类对象保存数据!

​ 而当需要操作数据的时候,我们通过传入 placeholder 对象给 Any ,本质就是 holder 的指针拿到了 placeholder ,就形成了一种多态的机制来操控 placeholder 类!

cpp 复制代码
class Any
{
private:
    class holder 
    {
      	...  
    };
    
    template <class T>
    class placeholder : holder // 用来存放其它数据类型的类
    {
        T _val;
    };
    
    holder* _content; // 父类的指针
};

主体框架

​ 结合下面的代码,我们可以看到思路就是实例化一个 Any 类的时候不需要传入一个特定类型参数,因为它会被内部的 _content 指针所拿到,拿到之后其实就与 placeholder 类形成了多态,此时操作 _content 指针就相当于控制 placeholder 类,然后 Any 对象的操作就可以调用 placeholder 类的接口完成操作!

cpp 复制代码
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++ 一些语法特性罢了!

cpp 复制代码
#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
构造
拷贝
析构
析构
相关推荐
利刃大大6 小时前
【高并发服务器:HTTP应用】十四、Util工具类的设计与实现
服务器·http·高并发·项目·cpp
zhanglianzhao6 小时前
基于云服务器自建Rustdesk远程桌面——以京东云为例
运维·服务器·京东云
可乐大数据6 小时前
Docker安装(基于云服务器ECS实例 CentOS 7.9系统)
服务器·docker·centos
菲兹园长6 小时前
微服务组件(E、L、N、O、G)
linux·服务器·gateway
LBuffer6 小时前
破解入门学习笔记题二十五
服务器·前端·microsoft
richxu202510017 小时前
Linux本地部署deepseek大模型之 6. Windows本地连接远程云服务器中的MySQL常见错误的解决办法
linux·服务器·windows
Ryan ZX7 小时前
Ubuntu 升级OpenSSH10.2p1与OpenSSL3.5.4版本避坑实践
linux·运维·服务器·ubuntu
唐兴通个人8 小时前
新品上市咨询顾问新药上市顾问培训讲师唐兴通讲PMF从0到1天使用户种子用户冷启动问题
运维·服务器
serve the people8 小时前
Partial Prompt Templates in LangChain
服务器·langchain·prompt
七夜zippoe9 小时前
高性能网络编程实战:用Tokio构建自定义协议服务器
linux·服务器·网络·rust·tokio