使用typeid进行运行时类型识别(RTTI)
Runtime Type Identification
RTTI是运行阶段类型识别(Runtime Type Identification)的简称。在「程序在运行阶段确定对象类型」。
对象、引用、函数(包括函数模板特化)和表达式具有称为「类型」的性质,它限制了对这些实体所容许的操作,并给原本寻常的位序列提供了语义含义。下面我们从这几点来看一下如何动态获取类型名。Ps:is_same也可以进行编译期间判断。
typeid
简介
typeid(xxx)返回typeinfo类型的对象, 通过返回的这个对象可以获取变量、模板、函数、指针以及其他一些表达式的类型名字。
「需要注意」的是,对于多态指针引用的对象, 他会返回"抽象类"的名字。
返回的名字具体由编译器的实现所决定的,标准只要求实现为每个类型返回唯一的字符串。
头文件
#include <typeinfo>
示例
编译并运行如下代码,观察结果。
#include <iostream>
#include <typeinfo>
#include <source_location>
class A
{
};
// 定义一个打印类型的方法
template <typename T>
void print_type(T t)
{
const auto &typeinfo_ = typeid(t);
// same as auto typeinfo = typeid(T);
std::cout << std::source_location::current().function_name() << ": "
<< typeinfo_.name() << " hascode: " << typeinfo_.hash_code() << std::endl;
}
// 判断一个数是否是double
template <typename T>
bool is_double(const T& t)
{
return typeid(t) == typeid(double);
// return typeid(T) == typeid(double);
}
bool is_zero()
{
if(is_double()){}
}
int main(int argc, char *argv[])
{
try
{
// 直接获取类型的类型, 即可以判断模板的类型
std::cout << typeid(A).name() << " ";
std::cout << typeid(const A &).hash_code() << std::endl;
std::cout << std::string(100, '=') << std::endl;
// 获取对象的类型
A a;
const A &b = a;
print_type(a);
print_type(b);
// 获取基本类型变量的类型
double d = 0.;
print_type(d);
int i = 1;
print_type(i);
// 获取指针的类型
double *d_ptr = new double;
print_type(d_ptr);
//
std::cout << std::string(100, '=') << std::endl;
// 这里注意一点,? 的优先级
std::cout << "d is " << (is_double(d) ? "" : "not ") << "a double value\n";
std::cout << "i is " << (is_double(i) ? "" : "not ") << "a double value\n";
// 获取表达式的类型
std::cout << "i+1.0 is " << (is_double(i + 1.0) ? "" : "not ") << "a double value\n";
std::cout << std::string(100, '=') << std::endl;
// 函数
print_type(main);
print_type(nullptr);
}
catch (std::bad_typeid &e)
{
// 需要注意,当 typeid 表达式中的实参为空值时抛出的异常
std::cout << e.what() << std::endl;
}
// 可以点进入看一下源码,在不同的标准下有不同的实现。
}
输出
class A 2500143284403227906
====================================================================================================
print_type: class A hascode: 2500143284403227906
print_type: class A hascode: 2500143284403227906
print_type: double hascode: 12638230079955414429
print_type: int hascode: 12638232278978672507
print_type: double * __ptr64 hascode: 17651627952895229888
====================================================================================================
d is a double value
i is not a double value
i+1.0 is a double value
====================================================================================================
print_type: int (__cdecl*)(int,char * __ptr64 * __ptr64 const) hascode: 7815614184493192578
print_type: std::nullptr_t hascode: 11608250240117119972
继承关系下的类型信息
第一种
#include <iostream>
#include <typeinfo>
#include <source_location>
class A
{
};
class B : public A
{
};
class C : A
{
};
template <typename T>
void print_type(T t)
{
const auto &typeinfo_ = typeid(t);
// same as auto typeinfo = typeid(T);
std::cout << std::source_location::current().function_name() << ": "
<< typeinfo_.name() << " hascode: " << typeinfo_.hash_code() << std::endl;
}
int main(int argc, char *argv[])
{
try
{
A *a = new A;
B *b = new B;
C *c = new C;
print_type(a);
print_type(b);
print_type(c);
}
catch (std::bad_typeid &e)
{
// 当 typeid 表达式中的实参为空值时抛出的异常
std::cout << e.what() << std::endl;
}
// 可以点进入看一下源码,在不同的标准下有不同的实现。
}
// 输出
// print_type: class A * __ptr64 hascode: 7975377594211774730
// print_type: class B * __ptr64 hascode: 9847873382690236939
// print_type: class C * __ptr64 hascode: 9223567381966856156
「注意看下面」
需要注意的是,对于多态指针引用的对象, 他会返回父类的名字。
A *a = new A;
A *b = new B; // 这里打印的是A类型
auto c = new C;
print_type(a);
print_type(b);
print_type(c);
// 输出
print_type: class A * __ptr64 hascode: 7975377594211774730
print_type: class A * __ptr64 hascode: 7975377594211774730
print_type: class C * __ptr64 hascode: 9223567381966856156
练习 - Json反序列化
这里我们使用了两种方式判断类型, std::is_same_v<> 我们将在下一次课程中细说, 大家先看一下。
#include <typeinfo>
#include <vector>
#include <string>
#include <map>
#include <array>
#include <string_view>
#include <iostream>
#include <memory>
class PPJson
{
public:
// shared data
static PPJson parse(const std::string &strJson)
{
PPJson json;
json._values = std::make_shared<std::map<std::string, std::string>>(); // 找到第一个引号
auto keyStart = strJson.find('"');
while (keyStart != std::string::npos)
{
// 找到下一个引号
auto keyStop = strJson.find('"', ++keyStart);
// 取中间的就是 key
std::string key = strJson.substr(keyStart, keyStop - keyStart);
// value是冒号分隔的
auto valueStart = strJson.find(':', keyStop);
// 结束的是 ,
auto valueStop = strJson.find_first_of(",}", ++valueStart);
// 获取中间的数据
std::string value = strJson.substr(valueStart, valueStop - valueStart);
//
// if (value.front() == "\""){} // string
json._values->insert({key, value}); // _values[key] = value;
keyStart = strJson.find('"', ++keyStop);
}
return json;
}
std::string value(const std::string &key) const
{
return _values->at(key);
}
private:
std::shared_ptr<std::map<std::string, std::string>> _values;
};
// constexpr std::array<std::string_view, 3> user_keys = {"name", "age", "height"};
#if 0
// 编译时判断
// 统一转换
template <typename T>
bool assign(T &t, const std::string &value)
{
if constexpr (std::is_same_v<T, int>)
{
t = std::stoi(value);
return true;
}
else if constexpr (std::is_same_v<T, std::string>)
{
t = value;
return true;
}
else if constexpr (std::is_same_v<T, double>)
{
t = std::stod(value);
return true;
}
return false;
}
#endif
// 运行时判断 C++23增加 constexpr
template <typename T>
bool assign(T &t, const std::string &value)
{
if constexpr (typeid(T) == typeid(int))
{
t = std::stoi(value);
return true;
}
else if constexpr (typeid(T) == typeid(std::string))
{
t = value;
return true;
}
else if constexpr (typeid(T) == typeid(double))
{
t = std::stod(value);
return true;
}
return false;
}
struct User
{
std::string name;
int age = 0;
double height = 0;
// User &operator=(const PPJson &json)
// {
// assign(name, json.value("name"));
// assign(age, json.value("age"));
// assign(height, json.value("height"));
// return *this;
// }
friend std::ostream &operator<<(std::ostream &os, const User &user)
{
return os << "name: " << user.name << " age: " << user.age << " height: " << user.height;
}
};
bool fromJson(const PPJson &json, User &user)
{
assign(user.name, json.value("name"));
assign(user.age, json.value("age"));
assign(user.height, json.value("height"));
return true;
}
int main(int argc, char *argv[])
{
std::string strJson = R"({"name": "user", "age": 18, "height": 1.6})";
// User user = PPJson::parse(strJson);
if (User user; fromJson(PPJson::parse(strJson), user))
{
std::cout << user;
}
return 0;
}
拓展
三目运算符的优先级
考虑以下程序的结果
void test()
{
// 先算加法
int v1 = 2 + 1 > 2 ? 2 : 3;
std::cout << "v1: " << v1 << std::endl;
// 先算括号里面的,和v1一样
int v2 = (2 + 1) > 2 ? 2 : 3;
std::cout << "v2: " << v2 << std::endl;
// 先算后边的
int v3 = 2 + (1 > 2 ? 2 : 3);
std::cout << "v3: " << v3 << std::endl;
}
// 输出(VS2022 v143)
v1: 2
v2: 2
v3: 5

浙公网安备 33010602011771号