【高并发服务器】四、通用类型容器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
构造
拷贝
析构
析构
相关推荐
万山y3 小时前
WSL SSH 服务器一站式配置教程
运维·服务器·ssh
半梦半醒*3 小时前
Prometheus监控物理服务器
linux·运维·服务器·centos·prometheus
mzhan0173 小时前
Linux: network: 拔插网线 interface down/up测试的痛点
linux·服务器·测试·破坏性测试
恒创科技HK3 小时前
高防服务器分为哪几种?香港高防服务器有什么特点?
运维·服务器·安全
魔猴疯猿3 小时前
轻松搭建RTMP推流、WebRTC拉流服务器SRS服务,源码编译安装
服务器·elasticsearch·webrtc
WnHj3 小时前
DORIS 服务器宕机重启后出现的问题
运维·服务器
qq_339191143 小时前
aws ec服务器统一为国内时间。ec 设置上海
服务器·云计算·aws
isyangli_blog11 小时前
(6)数据中心、台式(塔式)服务器、机架式服务器、刀片式服务器
运维·服务器
tq0211 小时前
Cookie和Seeion在客户端和服务端的角色作用
运维·服务器·安全