【高并发服务器】四、通用类型容器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
构造
拷贝
析构
析构
相关推荐
qq_4798754315 小时前
X-Macros(1)
linux·服务器·windows
jun_bai17 小时前
python写的文件备份网盘程序
运维·服务器·网络
Warren9817 小时前
Python自动化测试全栈面试
服务器·网络·数据库·mysql·ubuntu·面试·职场和发展
欢喜躲在眉梢里17 小时前
CANN 异构计算架构实操指南:从环境部署到 AI 任务加速全流程
运维·服务器·人工智能·ai·架构·计算
云飞云共享云桌面18 小时前
无需配置传统电脑——智能装备工厂10个SolidWorks共享一台工作站
运维·服务器·前端·网络·算法·电脑
福尔摩斯张18 小时前
《C 语言指针从入门到精通:全面笔记 + 实战习题深度解析》(超详细)
linux·运维·服务器·c语言·开发语言·c++·算法
虚伪的空想家19 小时前
arm架构服务器使用kvm创建虚机报错,romfile “efi-virtio.rom“ is empty
linux·运维·服务器·javascript·arm开发·云原生·kvm
深藏bIue19 小时前
linux服务器mysql目录下的binlog文件删除
linux·服务器·mysql
虾..19 小时前
Linux 进程状态
linux·运维·服务器
只想安静的写会代码21 小时前
网卡信息查询、配置、常见故障排查
linux·服务器·windows