Any模拟实现

仿muduo库项目铺垫2!
感谢佬们支持!

一、为什么需要 Any?

在实现一个高并发网络服务器时,每一个 Connection 对象负责管理一条连接,而连接不可避免地要涉及应用层协议的处理。

为了控制处理节奏,Connection 中需要保存一个协议处理上下文

问题来了:应用层协议千千万万,HTTP、WebSocket、自定义协议......每种协议的上下文数据结构都不一样。如果把协议类型写死在 Connection 里,耦合度极高,换个协议就得改底层代码。

理想的设计是: 上下文可以是任意类型,Connection 不关心里面存的是什么,只管存和取。

在 C 语言中,这个问题可以用 void* 解决,但类型信息完全丢失,不安全。

Boost 库和 C++17 标准库为我们提供了 any 来解决这个问题。但如果项目需要兼容 C++11/14,或者希望减少第三方库依赖,就需要自己实现一个 Any 类


二、如何设计 Any?

2.1 首先想到模板

最直接的思路是用模板:

复制代码
template<class T>
class Any {
    T _val;
};

但这样有个致命问题:使用时必须显式指定类型

复制代码
Any<int> a = 10;       // 必须写<int>
Any<string> b = "hi";  // 必须写<string>

这又不方便,看着也不优雅

2.2 类型擦除:嵌套父子类结构

解决思路是类型擦除(Type Erasure),核心思想如下:

  • 定义一个非模板基类 holder
  • 定义一个模板子类 placeholder<T>,用来存储真正的数据
  • Any 类只持有一个父类指针 holder*

这样,Any 类本身不是模板,使用时无需指定类型,而真正的数据类型信息被"藏"进了子类里。

复制代码
Any
 └── holder* _content        ← 父类指针,类型固定
          ↑
     placeholder<int>        ← 子类,存 int
     placeholder<string>     ← 子类,存 string
     placeholder<MyStruct>   ← 子类,存任意类型

每次赋值,都在堆上 new 一个对应类型的 placeholder<T> 对象,旧的对象被析构释放。对外只暴露父类指针,外部感知不到类型变化,这就是类型擦除


三、完整代码实现

我们先造一个在外部的父类,其中都有虚函数

cpp 复制代码
// 非模板基类,用于类型擦除
    class holder {
    public:
        virtual ~holder() {}
        virtual const std::type_info& type() = 0;//返回类型信息
        virtual holder* clone() = 0;
    };

子类继承父类

cpp 复制代码
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(_val); 
                }
            public:
                T _val;
        };

整个any类我们这样设计

cpp 复制代码
holder *_content;
    public:
        Any():_content(NULL) {}

        template<class T>
        Any(const T &val)
        {}

        
        Any(const Any &other)
         {}
        ~Any() 
         {}

        Any &swap(Any &other) 
        {
            
        }

        // 返回子类对象保存的数据的指针
        template<class T>
        T *get() 
        {
            
        }
        //赋值运算符的重载函数:
        template<class T>
        Any& operator=(const T &val) 
        {
            
        }
        Any& operator=(const Any &other) 
        {
            
        }

实现!

复制代码
#include <iostream>
#include <typeinfo>
#include <cassert>

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(_val);
        }

    public:
        T _val;
    };

    holder* _content; // 父类指针,对外隐藏真实类型

public:
    Any() : _content(nullptr) {}

    template<class T>
    Any(const T& val) : _content(new placeholder<T>(val)) {}

    // 拷贝构造:克隆一份新的子类对象
    Any(const Any& other) : _content(other._content ? other._content->clone() : nullptr) {}

    ~Any() { delete _content; }

    Any& swap(Any& other) {
        std::swap(_content, other._content);
        return *this;
    }

    // 取出数据:将父类指针转为子类指针,再取成员
    template<class T>
    T* get() {
        assert(typeid(T) == _content->type());
        return &((placeholder<T>*)_content)->_val;
    }

    // 赋值重载:构造临时对象后 swap,旧数据随临时对象析构
    template<class T>
    Any& operator=(const T& val) {
        Any(val).swap(*this);
        return *this;
    }

    Any& operator=(const Any& other) {
        Any(other).swap(*this);
        return *this;
    }
};

四、关键细节解析

4.1 为什么赋值用 swap 而不是直接赋值?

复制代码
template<class T>
Any& operator=(const T& val) {
    Any(val).swap(*this);  // 构造临时对象,交换指针,临时对象析构时释放旧数据
    return *this;
}

这是 C++ 中经典的"copy-and-swap"惯用法:

  1. 用新值构造一个临时 Any 对象(此时新数据在临时对象里)
  2. 把临时对象的 _contentthis->_content 交换
  3. 临时对象析构,自动释放旧数据

好处是异常安全 ,新对象构造失败时,this 的数据完好无损。

4.2 拷贝构造为什么要 clone?

复制代码
Any(const Any& other) : _content(other._content ? other._content->clone() : nullptr) {}

不能直接复制指针,否则两个 Any 对象指向同一块内存,析构时会 delete 两次导致崩溃。

clone() 是纯虚函数,子类实现时 new 一个新的 placeholder<T> 对象并返回,实现深拷贝。

4.3 get() 的类型萃取

复制代码
template<class T>
T* get() {
    assert(typeid(T) == _content->type());
    return &((placeholder<T>*)_content)->_val;
}

从外部看,get<int>() 就像是"把 int 类型从 Any 中萃取出来",有点类似 C++ 模板元编程中**类型萃取(type traits)**的感觉。

区别在于:类型萃取是编译期行为,而 get() 是运行期通过 typeid 做类型校验,类型不匹配会触发 assert,保证安全。


五、测试验证

复制代码
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;  // 输出 10

    a = std::string("nihao");
    std::string* ps = a.get<std::string>();
    std::cout << *ps << std::endl;  // 输出 nihao

    {
        Test t;       // 构造
        a = t;        // 拷贝(构造 placeholder<Test>)
    }               // t 析构
                    // a 出作用域,placeholder<Test> 析构 → Test 析构
    return 0;
}

输出:

复制代码
10
nihao
构造
拷贝
析构
析构

注意到两次析构:一次是 t 出作用域,一次是 a 持有的那份拷贝在 a 析构时释放,符合预期。


六、与 C++17 std::any 的对比

C++17 标准库提供了 std::any,底层原理与本文实现基本一致,也是类型擦除。

复制代码
#include <any>

std::any a = 10;
std::any b = std::string("hello");

int val = std::any_cast<int>(a);        // 取出 int
std::string s = std::any_cast<std::string>(b);  // 取出 string

std::any_cast 对应本文的 get<T>(),类型不匹配时抛出 std::bad_any_cast 异常(比 assert 更优雅)。

如果项目支持 C++17,优先使用标准库的 std::any;需要兼容低版本或避免依赖时,本文的手动实现完全可以胜任。


七、总结

对比项 void* Any(本文实现) std::any(C++17)
类型安全 ✅ assert 校验 ✅ 异常校验
需要显式类型 取出时需要 取出时需要 取出时需要
C++11 兼容
实现复杂度 无需实现

手动实现 Any 的核心思路只有一个:用非模板基类指针持有模板子类对象,实现类型擦除 。理解了这个,再去看 std::functionstd::any 的源码,会发现异曲同工。

总结

做总结,本篇博客介绍了通用类型any及其模拟实现!

水平有限,还请各位大佬指正。如果觉得对你有帮助的话,还请三连关注一波。希望大家都能拿到心仪的offer哦。

每日gitee 侠:今天你交gitee了嘛

相关推荐
[J] 一坚16 小时前
嵌入式高手C
c语言·开发语言·stm32·单片机·mcu·51单片机·iot
借雨醉东风16 小时前
程序分享--常见算法/编程面试题:旋转矩阵
c++·线性代数·算法·面试·职场和发展·矩阵
云泽80817 小时前
笔试算法 - 双指针篇(二):四大经典求和题型 + 有效三角形计数问题
c++·算法
十五年专注C++开发18 小时前
WaitingSpinnerWidget: 一个高度可配置的自定义Qt等待加载动画组件
开发语言·c++·qt·waitingspinner
qeen8718 小时前
【数据结构】树的基本概念及存储
c语言·数据结构·c++·学习·
一江寒逸18 小时前
数据结构与算法之美:串(字符串)——从基础操作到KMP模式匹配,吃透面试最高频的字符串考点
数据结构·面试·职场和发展
王老师青少年编程18 小时前
csp信奥赛C++高频考点专项训练之贪心算法 --【区间贪心】:种树
c++·算法·贪心·csp·信奥赛·区间贪心·种树
hi_ro_a19 小时前
C++ 哈希表封装 unordered_map /unordered_set
数据结构·c++·算法·哈希算法
c++之路19 小时前
C++ 动态内存
java·jvm·c++
pluviophile_s1 天前
第18讲:⾃定义类型:结构体
c语言·笔记