首页 > 编程语言 >【C++】any类的介绍与模拟实现

【C++】any类的介绍与模拟实现

时间:2025-01-09 19:02:39浏览次数:3  
标签:std const C++ Any operand type any 模拟

目录

一、any类简介

1. std::any 的基本特性

2. std::any 的成员函数

2.1 构造函数

2.2 赋值操作符

2.3 has_value

2.4 type

3. std::any_cast

3.1 std::any_cast 的基本用法

3.2 std::any_cast 的安全检查

4. std::any 的应用场景

二、模拟实现any类

any类的设计思想

设计框架

完整代码

测试用例


在日常编程中,我们可能会遇到这么一个场景:需要一个类型可以接收任意类型的变量,并且在需要使用该变量的时候还能恰当的进行转换。不难想到,C语言中的万能指针void*可以满足我们上述的需求。但void*的使用相对繁琐,且难免会涉及到大量的内存管理操作,这无疑加大了我们编程的复杂度。而在C++17中,any类的出现很好的解决了我们上述的问题。

一、any类简介

std::any 是 C++17 引入的一个标准库类型,用于表示一个可以存储任意类型数据的容器。与 std::variant 不同,std::any 不限制存储的类型,因此它可以用来存储任意的对象。它的设计目标是提供一种简单的方式来存储和检索任意类型的值,而不需要像 void* 那样手动管理类型信息。

1. std::any 的基本特性

  • 任意类型的存储std::any 可以存储任何可拷贝构造的类型。
  • 类型安全std::any 提供了类型安全的访问,确保在访问值时不会发生类型错误。
  • 动态类型std::any 可以在运行时存储不同类型的对象,而无需在编译时指定类型。

2. std::any 的成员函数

2.1 构造函数
  • std::any():默认构造函数,创建一个空的 std::any 对象。
  • std::any(const std::any& other):拷贝构造函数,创建一个与 other 相同内容的 std::any 对象。
  • std::any(std::any&& other) noexcept:移动构造函数,创建一个从 other 移动内容的 std::any 对象。
  • template<typename T> std::any(T&& value):从任意类型 T 构造 std::any 对象。
#include <any>
#include <iostream>

int main() {
    std::any a = 42;          // 存储一个 int
    std::any b = "hello";     // 存储一个 const char*
    std::any c = std::string("world"); // 存储一个 std::string
}
2.2 赋值操作符
  • std::any& operator=(const std::any& other):拷贝赋值操作符。
  • std::any& operator=(std::any&& other) noexcept:移动赋值操作符。
  • template<typename T> std::any& operator=(T&& value):从任意类型 T 赋值。
#include <any>
#include <iostream>

int main() {
    std::any a;
    a = 42;                  // 存储一个 int
    a = "hello";             // 存储一个 const char*
    a = std::string("world"); // 存储一个 std::string
}
2.3 has_value
  • bool has_value() const noexcept:检查 std::any 是否包含一个值。
#include <any>
#include <iostream>

int main() {
    std::any a;
    if (!a.has_value()) {
        std::cout << "a is empty\n";
    }
    a = 42;
    if (a.has_value()) {
        std::cout << "a has a value\n";
    }
}
2.4 type
  • const std::type_info& type() const noexcept:返回当前存储值的类型信息。
#include <any>
#include <iostream>

int main() {
    std::any a = 42;
    if (a.type() == typeid(int)) {
        std::cout << "a contains an int\n";
    }
}

3. std::any_cast

std::any_cast 是用于从 std::any 对象中安全地提取值的工具。它提供了类型安全的访问方式,如果类型不匹配,则抛出 std::bad_any_cast 异常。

3.1 std::any_cast 的基本用法
  • template<typename T> T any_cast(std::any& operand):提取 std::any 中的值,返回 T 类型的引用。
  • template<typename T> T any_cast(const std::any& operand):提取 std::any 中的值,返回 T 类型的常量引用。
  • template<typename T> T any_cast(std::any&& operand):提取 std::any 中的值,返回 T 类型的右值引用。
  • template<typename T> const T* any_cast(const std::any* operand):提取 std::any 中的值,返回 T 类型的指针。
  • template<typename T> T* any_cast(std::any* operand):提取 std::any 中的值,返回 T 类型的指针。
#include <any>
#include <iostream>

int main() {
    std::any a = 42;
    int value = std::any_cast<int>(a);
    std::cout << "Value: " << value << '\n';

    // 如果类型不匹配,会抛出异常
    try {
        std::string str = std::any_cast<std::string>(a);
    } catch (const std::bad_any_cast& e) {
        std::cout << "Caught exception: " << e.what() << '\n';
    }
}
3.2 std::any_cast 的安全检查

std::any_cast 也可以返回一个指针,这样可以避免抛出异常,并且可以在没有值的情况下返回 nullptr

#include <any>
#include <iostream>

int main() {
    std::any a = 42;
    if (int* value = std::any_cast<int>(&a)) {
        std::cout << "Value: " << *value << '\n';
    } else {
        std::cout << "a does not contain an int\n";
    }
}

4. std::any 的应用场景

  • 通用数据存储std::any 可以用于存储任意类型的数据,适合在需要存储不同类型数据的场景中使用。
  • 动态类型处理:在运行时需要处理不同类型的数据时,std::any 可以作为一种通用的解决方案。

二、模拟实现any类

any类的设计思想

⾸先Any类肯定不能是⼀个模板类,否则编译的时候 Any<int> a, Any<float>b,需要传类型作为模板参数,也就是说在使⽤的时候就要确定其类型。显然,我们希望的是直接定义一个Any对象能够接收不同类型的对象。如:Any a = std::string("hello"); 或 Any a = 10;。

我们如何在不进行显式实例化的同时还能接收各类型的对象呢?我们知道,想要接收任意类型的对象,就务必要使用模板,但模板参数的实际类型是在编译时才进行确认的,但我们又无法直接对Any类使用模板,因此Any类的成员变量也一定不是模板,而是一个既定类型的变量。因此考虑Any内部设计⼀个模板容器subholder类,该类可以保存各种类型数据。

而因为在Any类中⽆法定义这个subholder对象或指针,因为any也不知道这个类要保存什么类型的数据,因此⽆法传递类型参数。所以,定义⼀个基类placehoder,让subholder继承于placeholder,⽽Any类保存⽗类指针即可。

当需要保存数据时,由于模板可以自行推导所传入参数的类型,因此我们在Any类构造函数赋值运算符重载函数的设计中可以使用模板函数,然后在函数内部new⼀个带有模板参数的子类subholder对象出来保存数据,然后让Any类中的⽗类指针指向这个子类对象就搞定了。例如:

template <typename T>
Any(const T &operand)
 : _content(new subholder<T>(operand))
{}

这其实是使用了多态的思想:假设Any类中有一个非模板成员变量content,要使得该变量可以接收任意类型的值,我们可以使用“多态的思想”。在C++多态中,基类对象可以接收子类对象——会进行“切片处理”保证赋值的正确性,且在需要的时候,可以对基类对象进行强制转换使其转换为子类对象。

设计框架

  1. Any 类的设计目标

    • 提供一个通用的容器,能够存储任意类型的数据(类似于 std::any)。
    • 支持在运行时获取存储数据的类型信息(通过 typeid)。
    • 支持深拷贝、赋值操作,以及类型安全的值提取(any_cast)。
  2. 核心设计思想

    • 使用多态模板技术实现类型擦除(Type Erasure)。
    • 通过派生类(subholder<T>)存储具体类型的数据,并通过基类指针(placeholder*)进行管理。
  3. 类层次结构

    • placeholder 类
      • 是一个纯虚基类,定义了接口(type() 和 clone()),不能直接实例化。
      • 所有具体类型的数据存储类(subholder<T>)都继承自它。
    • subholder<T> 类
      • 继承自 placeholder,用于存储具体类型的数据(T _val)。
      • 实现了 type() 和 clone() 接口,提供类型信息和深拷贝功能。
class Any
    {
        // 基类
        class placeholder // 包含纯虚函数,是抽象类,不能直接实例化对象
        {
        public:
            virtual ~placeholder() {
                /*placeholder 类是一个纯虚类,
                它的析构函数需要提供一个实现,否则会导致链接错误。
                纯虚析构函数的实现通常是一个空函数体。*/
            }
            virtual const std::type_info &type() = 0; // 返回参数的类型
            virtual placeholder *clone() = 0;   // 克隆出一个相同的对象,实现深拷贝
        };

        template <typename T>
        class subholder : public placeholder // 子类
        {
        public:
            subholder(const T &operand) : _val(operand) {}
            ~subholder() {}                       // 析构
            const std::type_info &type() override // 返回对象的数据类型
            {
                return typeid(T);
            }
            placeholder *clone() override
            {
                return new subholder(_val);
            }

        public:
            T _val; // 数据对象
        };

    private:
        placeholder *_content; // 定义基类指针
    public:
        Any() {};

        template <typename T>
        Any(const T &operand);

        bool has_value(); // 是否已经有值

        Any(const Any &operand); //拷贝构造

        template <typename T>
        Any &operator=(const T &operand); // 赋值运算符重载

        Any &operator=(Any operand); // 赋值运算符重载

        void swap(Any &operand); // 交换数据

        const std::type_info &type(); // 返回保存的对象的数据类型

        template <typename T>
        T *get_val(); // 获取指向数据对象的指针
    };

完整代码

#include <iostream>
#include <string>
#include <typeinfo>
#include <typeindex>
#include <cassert>

namespace MyAny
{
    class Any
    {
        class placeholder // 包含纯虚函数,是抽象类,不能直接实例化对象
        {
        public:
            virtual ~placeholder() {
                /*placeholder 类是一个纯虚类,
                它的析构函数需要提供一个实现,否则会导致链接错误。
                纯虚析构函数的实现通常是一个空函数体。*/
            }
            virtual const std::type_info &type() = 0; // 返回参数的类型
            virtual placeholder *clone() = 0;   // 克隆出一个相同的对象,实现深拷贝
        };

        template <typename T>
        class subholder : public placeholder // 子类
        {
        public:
            subholder(const T &operand) : _val(operand) {}
            ~subholder() {}                       // 析构
            const std::type_info &type() override // 返回对象的数据类型
            {
                return typeid(T);
            }
            placeholder *clone() override
            {
                return new subholder(_val);
            }

        public:
            T _val; // 数据对象
        };

    private:
        placeholder *_content; // 定义基类指针
    public:
        Any() : _content(nullptr) {};

        template <typename T>
        Any(const T &operand) : _content(new subholder<T>(operand)){};

        bool has_value() // 是否已经有值
        {
            return _content != nullptr;
        }

        Any(const Any &operand)
        {
            _content = operand._content == nullptr ? nullptr : operand._content->clone();
        }

        template <typename T>
        Any &operator=(const T &operand)
        {
            /*为operand构建⼀个临时对象出来,然后进⾏交换,这样临时对象销毁的时候,顺带原先
            保存的placeholder也会被销毁*/
            Any(operand).swap(*this);
            return *this;
        }

        Any &operator=(Any operand) // 构造一个新的临时对象,直接交换资源即可
        {
            
            // if(has_value()) {delete _content; _content = nullptr;} 使用拷贝交换惯用法,已经将资源托管给临时对象,无需这一步
            swap(operand);
            return *this;
        }
        void swap(Any &operand)
        {
            std::swap(_content, operand._content);
        }

        const std::type_info &type()
        {
            return _content->type();
        }

        template <typename T>
        T *get_val()
        {
            if (typeid(T) != type())
                assert(nullptr);
            return &(((subholder<T> *)_content)->_val);
        }
        ~Any()
        {
            if(has_value()) delete _content;
            _content = nullptr;
        }
    };

    template <typename T>
    T any_cast(Any &operand)
    {
        if (typeid(T) != operand.type())
            throw("type mismatch");
        return *(operand.get_val<T>());
    }

    template <typename T>
    T *any_cast(Any *operand)
    {
        if (typeid(T) != operand->type())
            throw("type mismatch");
        return operand->get_val<T>();
    }
}

测试用例

class Test
{
public:
    std::string _data;

public:
    Test(const std::string &data) : _data(data) { std::cout << "构造" << _data << std::endl; }
    Test(const Test &other)
    {
        _data = other._data;
        std::cout << "拷⻉" << _data << std::endl;
    }
    ~Test() { std::cout << "析构" << _data << std::endl; }
};

using namespace MyAny;
int main()
{
    // 测试用例1
    Any a;
    a = std::string("asdasdasd");
    std::string ret = any_cast<std::string>(a);
    std::cout << ret << std::endl;
     
    Any b = 10;
    std::cout << any_cast<int>(b) << std::endl;

    b = a;
    std::cout << *b.get_val<std::string>() << std::endl;
    std::cout << any_cast<std::string>(a) << std::endl;

    // // 测试用例2
    // Any a;
    // {
    //     Test t("1");
    //     a = t;
    // }
    // while(1);
    return 0;
}

标签:std,const,C++,Any,operand,type,any,模拟
From: https://blog.csdn.net/2301_76606232/article/details/145032271

相关文章

  • 【C++动态规划 数学】1039. 多边形三角剖分的最低得分|2130
    本文涉及知识点C++动态规划数学LeetCode1039.多边形三角剖分的最低得分你有一个凸的n边形,其每个顶点都有一个整数值。给定一个整数数组values,其中values[i]是第i个顶点的值(即顺时针顺序)。假设将多边形剖分为n-2个三角形。对于每个三角形,该三角形的值......
  • 1.9 CW 模拟赛 T2. array
    思路简单的考虑梦梦的决策点为\(k\)时,如何使最大子段和最大化容易想到以下的分类讨论完全在\([1,2k-2]\cup[2k+1,2n]\)中包含\(C_{2k-1},C_{2k}\)中的任意一个包含\(C_{2k-1},C_{2k}\)考试的时候想到了这里,问题在于如何高效的解决计算这\(3\)......
  • C++ 格式化输出 printf
    格式化输出的目的是先组织好格式,然后把变量替换进去。格式化说明格式化输出必会表保留指定位的小数在输出小数的时候,经常需要指定保留几位小数。比如保留2位小数要用%.2f,保留3位小数要用%.3f,依此类推。doublepi=3.1415926;printf("pi=%.3f",pi);补0输出补0输出常见......
  • 0108 模拟赛总结
    概况5题,共4h,我1,dry4。A-X魔法对预期:AC,实际:AC。题意给定\(a,b,x\)三个正整数,可以做若干次操作,每次操作可以把\(a\)或者\(b\)改为\(|a-b|\),问能否做若干次操作,使\(a\)或\(b\)变成\(x\)。思路暴力超时,考虑数学。令\(a>b\),则发现把\(b\)变成\(|a-b|\)......
  • 模拟ic入门——设计一个带隙基准Bandgap(三)高阶温度补偿与启动电路设计
    上一节我们介绍了Bandgap相关的参数,以及做了其中一个经典电路的电压模仿真,但如果对于温度系数有较高的要求,可以进行高阶温度补偿,本节我们来介绍高阶温度补偿,以及一些启动电路的设计,会附上一些经典的论文供大家学习一、电流模Bandgap首先我们进行电流模bandgap的仿真,运放我采......
  • 1.9 CW 模拟赛 赛时记录
    前言策略不变,继续搞看题\(4\)神秘,开骗\(\rm{T1}\)思路先考虑对于一个确定的\(a\)怎么做发现一个数能否被删除与删除的顺序无关,本质上是因为\(j,l\)并不因为操作而改变考虑到一个数能被删除,仅当其在前后缀中都不为最大值,也就是说可以\(\mathcal{O}(n)\)......
  • 2024 年 06 月 GESP C++ 一级真题解析
    ......
  • 深入理解C++智能指针:使用方法与注意事项
    智能指针是C++标准库提供的一种工具,用于管理动态分配的内存。相比传统的裸指针(rawpointer),智能指针能够自动管理资源,避免内存泄漏和悬空指针问题。本文将围绕智能指针的基本概念、常见类型及其使用方法展开,帮助你掌握这一强大的工具。一、什么是智能指针?智能指针是一个封装了......
  • C++/C语言的内存管理之虚拟内存
    C++/C语言的内存管理之虚拟内存一、虚拟内存1、组成2、特点3、目的二、栈区1、特点2、缺点三、堆区1、特点2、缺点3、相关四、全局静态区1、特点五、常量区1、特点六、代码区1、特点一、虚拟内存1、组成(1)栈区(Stack):存放局部变量、函数的参数。编译器自动分配和......
  • 17C++循环结构-(do-while循环)2——教学
    一、实例1、模拟法1在一次风之巅小学文艺汇演中,狐狸老师、尼克、格莱尔同台演出,其中个环节是拍手游戏,狐狸老师每1秒拍一次手,尼克每2秒拍次,格莱尔每4秒拍一次。三人同时开始拍第一次手,每人都拍10次。试编一程序,算一算观众可听到多少声拿声?按时间顺序,根据每个人的条件模拟拍手......