跟我学C++高级篇—运行时反射的std::type_index

一、C++中的轻度反射支持

在前面分析反射时给大家提到过,C++本身是不支持原生反射的,当然这并不是严格意义上的。这一个严格,就需要提到std::type_index,作为C++11引入的类,其包装了std::type_info并提供值语义的行为。而type_info又对type_id运算符提供了返回值封装。它们三个一起,组成了基础的C++运行时类型信息(RTTI)系统。
所以,大家可以把type_index当作一种支持轻度反射的类型封装,它可以直接在标准容器中应用。从而减少了开发者从底层开始处理类型的复杂度。

二、std::type_index、type_info和typeid

它们三个在C++的开发中,可能是比较容易让开发者迷惑的应用。首先开发者要明白typeid是一种运算符,它支持对类型和表达式的类型信息查询。主要应用于多态和静态类型标识时使用(比较类型是否相同)。大家不要把它当成一个函数,那样虽然用不错,但理解上就有问题了。其次,typeid的返回值被封装成type_info。作为一个标准库的类,它用来描述类型的运行时信息。大家可以把它们两个当成C++在早期为RTTI提供的一种基础接口行为,而type_index可以视作对RTTI向更高应用的一种尝试。std::type_index作为对std::type_info指针的一种封装,提供了一种更轻量级的完善性应用。虽然type_index和type_info有着不少的不同(是否可移动可拷贝等),但真正对于开发者的关键却在于它可以直接在标准库的容器中直接使用,这就显得方便了很多。
它们三属于一种从低级到高级不断封装应用的过程,或者说抽象的过程。C++不是多么高级的语言,所以它的抽象的脚步要小得很多。这个大家要理解。一般来说临时比较应用建议使用 typeid;而如果进行复杂的数据类型分类处理则推荐使用 std::type_index。另外type_info::name()有一个重要问题,它返回的字符串是编译器特定支持的,可能无法可读的名称。当然,大家可以利用一些第三方库进行处理这个name。

三、std::type_index的应用

std::type_index的应用在实际场景中应用不少,它提供类型安全的编译时和运行时的类型信息。主要的场景包括:

  1. 基础的类型映射表
    在一些基础的数据类型管理中,可以使用std::type_index来与STL中的标准容器一起进行类型映射处理
  2. 工厂模式中的应用
    利用std::type_index可以进行动态创建不同类型的效果
  3. 提供类型擦除的应用
    利用模板技术与std::type_index配合,从而对类型进行擦除并随时复现实际的类型
  4. 消息分发应用
    利用std::type_index进行不同类型的动态消息分发,有点类似事件处理
  5. 序列化处理
    可以通过std::type_index来动态的处理不同的数据类型的序列化和反序列化

但std::type_index在实际应用中也有一些需要注意的问题:

  1. 哈希冲突处理
    只要存在哈希就需要重视哈希冲突的存在,虽然有可能这种情况比较少见
  2. 类型名称的可移植性
    就是需要处理不同平台,不同编译器获取的类型名称导致的代码的可移植性的问题。特别是在跨平台开发中更为重要。
  3. 异常处理的问题
    typeid在处理空指针时会抛出异常,需要开发者注意
  try {
    A* pNull = nullptr;
    const std::type_info& info = typeid(*pNull); // 抛出异常
  } catch (const std::bad_typeid& e) {
    std::cout << "bad_typeid exception: " << e.what() << std::endl;
  }
  1. 多态中的应用
    在多态中操作时,会返回实际的类型信息
 Base* pBase = new Derived();
    
 // 多态中typeid
 std::cout << "static type: " << typeid(pBase).name() << std::endl;    // Base*
 std::cout << "dynamic type: " << typeid(*pBase).name() << std::endl;   // Derived

四、 例程分析

先看一个基础的应用 :

#include <functional>
#include <iostream>
#include <typeindex>
#include <typeinfo>
#include <unordered_map>

class TypeDemo {
private:
  std::unordered_map<std::type_index, std::string> typeMap;
  std::unordered_map<std::type_index, std::function<void()>> typeMapRun;

public:
  template <typename T> void setType(const std::string &type, std::function<void()> func = nullptr) {
    std::type_index idx = typeid(T);
    typeMap[idx] = type;
    if (func) {
      typeMapRun[idx] = func;
    }
  }

  template <typename T> std::string getType() const {
    auto it = typeMap.find(typeid(T));
    return it != typeMap.end() ? it->second : "unknown";
  }

  template <typename T> void runFunc() {
    auto it = typeMapRun.find(typeid(T));
    if (it != typeMapRun.end()) {
      it->second();
    }
  }

  std::string getType(const std::type_index &idx) const {
    auto it = typeMap.find(idx);
    return it != typeMap.end() ? it->second : "unknown";
  }
};

void typeExample() {
  TypeDemo tDemo;

  tDemo.setType<int>("integer type");
  tDemo.setType<double>("double type", []() { std::cout << "run double " << std::endl; });
  tDemo.setType<std::string>("string type");

  std::cout << "int type info: " << tDemo.getType<int>() << std::endl;
  std::cout << "double type info: " << tDemo.getType<double>() << std::endl;

  tDemo.runFunc<double>();
}
int main() {
  typeExample();
  return 0;
}

上面的代码其实还是比较好理解的,而且基于此代码可以很顺利的延展到其它的功能应用。

五、总结

正是因为C++的反射支持的有点差劲,虽然在后期的新标准中不断的进行完善,但仍然还是没有让广大的开发者感到满足。不过有进步总比不闻不问还是好的,至于什么时候才能够将反射比较完善的增加到标准库中,这就需要看大佬们的心情了。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值