C++之Any类的模拟实现

1.什么是 Any 类?

Any 类(类似于 C++17 的 std::any )是一种灵活的容器,它允许在运行时存储和操作任意类型 的单个值。它的核心价值在于类型擦除 ,使得程序可以在编译时不需要知道具体的数据类型。例如在 C++ 中,容器(如 std::vector)通常需要确定其存储的类型。Any 类打破了这一限制,允许存储来自配置文件的不同类型参数(整数、字符串、布尔值等),在不需要模板的情况下,设计接受和返回任意数据的函数或类。

2.类型擦除和多态

2.1 抽象基类:holder

holder 是实现多态的基础。它是一个抽象类,定义了所有具体存储器必须遵循的接口。

|------------|-----------------------------------------------------------|
| ~holder() | 保证通过基类指针释放内存时,能调用到派生类的析构函数,防止内存泄漏。 |
| type() | 返回存储数据的 typeid 信息。返回引用是为了避免拷贝不可拷贝的 std::type_info 对象。 |
| clone() | 实现多态克隆,是保证 Any 对象具有值语义的关键。 |

cpp 复制代码
    // 抽象基类,用于统一管理不同类型的数据
    class holder {
        public:
            // 虚析构函数,确保派生类对象能被正确销毁
            virtual ~holder() {}

            // 纯虚函数:获取子对象保存的数据类型信息 (std::type_info)
            virtual const std::type_info &type()  =0 ; 

            // 纯虚函数:克隆当前 holder 对象,用于实现深拷贝
            virtual holder *clone()=0 ; 
    };

2.1 抽象基类:placeholder<T>

模板类是真正的存储器。它为每种类型 T 生成一个具体的类,并继承自 holder

cpp 复制代码
    template<class T>
    class placeholder: public holder {
        public:
            // 构造函数:接受一个类型 T 的常量引用,并初始化内部存储的数据
            placeholder(const T &val): _val(val) {} // 注意:代码中变量名是 _val,与前面的 T_val 略有不同

            // 实现基类的 type() 纯虚函数
            // 获取子类对象保存的数据类型 (返回类型 T 的 std::type_info)
            virtual const std::type_info &type() { return typeid(T); }

            // 实现基类的 clone() 纯虚函数
            // 针对当前的对象自身,克隆出一个新的子类对象 (实现深拷贝)
            // 注意:这里使用 _val 成员进行构造,前提是 T 具有可用的拷贝构造函数
            virtual holder *clone() { return new placeholder(_val); }

        public:
            // 存储实际数据值 T 的成员变量
            T _val; // 注意:代码中变量名是 _val,与前面 T_val 略有不同
    };

3.Any类的接口与实现细节

cpp 复制代码
class Any {
    // private 成员,用于存储指向实际数据的指针
    holder *_content;
public:
    // 默认构造函数
    // 初始化 Any 对象为"空"状态,内部指针指向空 (NULL)
    Any() : _content(NULL) {} 

    // 模板构造函数
    // 接受任意类型 T 的值,创建一个新的 placeholder 对象来存储它
    template<class T>
    Any(const T &val) : _content(new placeholder<T>(val)) {} 

    // 拷贝构造函数 (实现深拷贝)
    // 检查源对象 (other) 是否为空,如果不为空,则调用其 _content->clone() 方法进行深拷贝
    Any(const Any &other)
        : _content(other._content ? other._content->clone() : NULL) {} 

    // 析构函数
    // 释放 _content 指针指向的动态内存
    ~Any() { delete _content; } 

    // swap 成员函数 (用于实现高效的赋值操作)
    // 交换当前对象和另一个 Any 对象 (_other) 内部的 _content 指针
    Any &swap(Any &other) {
        // 使用 std::swap 交换两个 holder* 指针
        std::swap(_content, other._content); 
        return *this;
    }

    // 模板成员函数:获取当前Any对象内部存储的数据(类型T)的指针
    // 返回子类对象保存的数据的指针
    template<class T>
    T *get() {
        // 运行时类型检查:
        // 想要获取的数据类型 (typeid(T)) 必须和当前 Any 对象实际存储的数据类型 (_content->type()) 一致
        assert(typeid(T) == _content->type());

        // 类型转换:将 _content (holder*) 强制转换为 (placeholder<T>*)
        // 然后访问其内部存储的成员变量 _val,并返回 _val 的地址 (&...)
        return &((placeholder<T>*)_content)->_val;
    }

    // 模板赋值运算符:允许将任意类型 T 的值赋给Any对象
    // 赋值运算符的重载函数
    template<class T>
    Any &operator=(const T &val) {
        // Copy-and-Swap 惯用法实现赋值操作:
        // 1. 创建一个临时的 Any 对象,它会通过模板构造函数存储 val 的副本。
        // 2. 将临时对象和当前对象 (*this) 进行指针交换 (swap)。
        // 为 val 构造一个临时的通用容器,然后与当前容器自身进行指针交换。
        // 临时对象释放的时候,原先保存的数据也就被释放了 (自动完成清理工作)。
        Any(val).swap(*this); 
        
        return *this;
    }
    // 拷贝赋值运算符:允许将另一个 Any 对象赋给当前 Any 对象
    Any &operator=(const Any &other) {
        // Copy-and-Swap 惯用法:
        // 1. 创建一个临时的 Any 对象,通过拷贝构造函数 Any(other) 实现对 other 的深拷贝。
        // Any(other) 构造了一个临时的 Any 对象,包含了 other 的数据副本。
        // 2. 将临时对象和当前对象 (*this) 进行指针交换 (swap)。
        Any(other).swap(*this); 
        
        // 3. 交换后,当前对象 (*this) 现在持有 other 的新副本,
        //    而临时对象持有当前对象原来的旧数据,临时对象析构时会自动清理旧数据。
        return *this;
    }
};

4.测试函数

cpp 复制代码
int main()
{
    // 1. 默认构造:创建一个空的 Any 对象
    Any a;

    // 2. 赋值操作:将整数 10 赋值给 Any 对象 a
    // 触发 Any::operator=(const T &val) 模板函数  
    a = 10;

    // 3. 获取数据:调用 get<int>() 获取内部存储的 int 数据的指针
    // 注意:这里必须指定正确的类型 int,否则会触发断言 (assert)
    int *pa = a.get<int>();

    // 4. 打印数据:解引用指针 pa,输出存储的整数值
    std::cout << *pa << std::endl; // 输出 10

    // 5. 重新赋值:将一个 std::string 对象赋值给 Any 对象 a
    // 原来的 int 数据会被自动释放和清理
    a = std::string("nihao");

    // 6. 再次获取数据:调用 get<std::string>() 获取内部存储的 string 数据的指针
    std::string *ps = a.get<std::string>();

    // 7. 打印数据:解引用指针 ps,输出存储的字符串
    std::cout << *ps << std::endl; // 输出 nihao

    // 8. 程序正常退出
    return 0;
}

输出:

相关推荐
CodeCraft Studio1 分钟前
JavaScript图表库 DHTMLX Diagram 6.1 重磅发布:全新PERT模式上线,项目可视化能力再升级!
开发语言·javascript·ecmascript·dhtmlx·图表开发·diagram·javascript图表库
Dxy12393102163 分钟前
Python的正则表达式如何做数据校验
开发语言·python·正则表达式
UP_Continue4 分钟前
C++--右值和移动语义
开发语言·c++
代码游侠16 分钟前
学习笔记——线程控制 - 互斥与同步
linux·运维·笔记·学习·算法
牛奶咖啡1326 分钟前
Linux常见系统故障案例说明并修复解决(下)
linux·服务器·文件系统挂载异常分析并修复·持久化挂载分区文件丢失故障修复·分析系统进程cpu占用率过高
222you31 分钟前
Java线程的三种创建方式
java·开发语言
云上漫步者37 分钟前
深度实战:Rust交叉编译适配OpenHarmony PC——unicode_width完整适配案例
开发语言·后端·rust·harmonyos
java_logo38 分钟前
Webtop Docker 容器化部署指南:基于浏览器的Linux桌面环境
linux·docker·容器·webtop·webtop部署教程·docker部署webtop·linux桌面
漫漫求41 分钟前
Java内存模型【JMM】、JVM内存模型
java·开发语言·jvm
wanderist.42 分钟前
2025年蓝桥杯省赛C++大学A组
c++·算法·蓝桥杯