C++17容器std::any和typeid()操作符

Aki 发布于 2022-11-20 984 次阅读


参考文章:C++17之std::any_C咖咖的博客-CSDN博客

std::any: 如何、何时和为什么 - C++团队博客 (microsoft.com)

C++17 ———— std::optional、std::variant和std::any - 弹吉他的小刘鸭 - 博客园 (cnblogs.com)

介绍

  一般来说,c++是一种具有类型绑定和类型安全性的语言。值对象声明为具有特定类型,该类型定义哪些操作是可能的以及它们的行为方式。值对象不能改变它们的类型。

    std: any是一种值类型,它能够更改其类型,同时仍然具有类型安全性。也就是说,对象可以保存任意类型的值,但是它们知道当前保存的值是哪种类型。在声明此类型的对象时,不需要指定可能的类型。

诀窍在于,对象同时拥有包含的值和使用typeid包含值的类型。因为这个值可以有任何大小,所以可以在堆上分配内存,鼓励实现避免小对象的动态分配。也就是说,如果分配一个字符串,对象将为该值分配内存并复制该字符串,同时也在内部存储分配的字符串。稍后,可以执行运行时检查来确定当前值的类型,并使用any_cast<该值的类型>获取值。

typeid()和typeid().name()、

在c++中,typeid用于返回指针或引用所指对象的实际类型,返回值类型是 class type_info。注意:typeid是操作符,不是函数!

class type_info 不支持拷贝构造,拷贝赋值和构造,只能使用typeid()操作符来返回对象。type_info 支持 == 和 != 。

运行时获知变量类型名称,可以使用 typeid(变量).name(),也就是成员函数name(),返回的是一个C语言风格的字符串。

补充:对非引用类型,typeid是在编译时期识别的,只有引用类型才会在运行时识别。

使用std::any、

需要包含头文件 #include<any>

any a;              //不确定类型,没有值
any b = 10;         //int,值为10
any c = 10.5;       //double,值为10.5
	
a = string("hello,world");   //std::string,值为hello,world

if (a.type() == typeid(string))
{
	cout << any_cast<const string&>(a) << endl;  //这里使用const & 是防止拷贝构造
}
	
if (b.type() == typeid(int))
{
	cout << any_cast<int>(b) << endl;
}

if (c.type() == typeid(double))
{
	cout << any_cast<double>(c) << endl;
}
	

可以声明std::any为空或由特定类型的值初始化。初始值的类型成为所包含值的类型。通过使用成员函数type(),可以根据任何类型的类型ID检查所包含值的类型。如果对象是空的,对象类型ID是typeid(void)。要访问包含的值,可以通过std::any_cast<对象类型>的方式。

auto s = any_cast<对象类型>(a);

如果转换失败,因为对象为空或包含的类型不匹配,则抛出std::bad_any_cast。因此,在不检查或不知道类型的情况下,最好实现以下功能:

try {
auto s = std::any_cast<std::string>(a);
...
}
catch (std::bad_any_cast& error) {
std::cerr << "EXCEPTION: " << error.what() << '\n';
}

注意,std::any_cast<>创建了一个传递类型的对象。如果将std::string作为模板参数传递给std::any_cast<>,它将创建一个临时string(一个prvalue),然后用它初始化新对象s。如果没有这样的初始化,通常最好转换为引用类型,以避免创建临时对象:

std::cout << std::any_cast<const std::string&>(a)<<endl;

要修改该值,需要转换为对应的引用类型:

std::any_cast<std::string&>(a) = "world";

还可以为std::any对象的地址调用std::any_cast。在这种情况下,如果类型匹配,则强制转换返回相应的地址指针;如果不匹配,则返回nullptr:

auto p = std::any_cast<std::string>(&a);
if (p) {
...
}

还要注意,值是使用衰减类型存储的(数组转换为指针,忽略顶层引用和const)。对于字符串常量,这意味着值类型是const char*。要检查type()并使用std::any_cast<>,必须使用以下类型:

std::any a = "hello";     // type() is const char*
if (a.type() == typeid(const char*)) { // true
...
}
if (a.type() == typeid(std::string)) { // false
...
}
std::cout << std::any_cast<const char*>(v[1]) << '\n'; // OK
std::cout << std::any_cast<std::string>(v[1]) << '\n'; // EXCEPTION

    std::any没有定义比较运算符(因此,不能比较或排序对象),没有定义hash函数,也没有定义value()成员函数。由于类型只在运行时才知道,所以不能使用泛型lambdas处理与类型无关的当前值。总是需要运行时函数std::any_cast<>来处理当前值。

可以将std::any任何对象放入容器中。

vector<any> anyVector = { 10,10.5,"hello,world",string("string") };
for (const auto& c : anyVector)
{
	if (c.type() == typeid(int))
	{
		cout << any_cast<int>(c) << endl;
	}
	else if (c.type() == typeid(const char*))
	{
		puts(any_cast<const char*>(c));
	}
	else if (c.type() == typeid(double))
	{
		cout << any_cast<double>(c) << endl;
	}
	else if (c.type() == typeid(string))
	{
		cout << any_cast<const string&>(c) << endl;
	}
}

std::any操作、

函数说明
constructors创建一个any对象(可能调用底层类型的构造函数)
make_any<T>()创建一个any对象(传递值,类型为T来初始化它)
.destructor销毁any对象
.operator =分配一个新值
.emplace<T>()分配一个类型为T的新值
.reset()销毁any对象的值(使对象为空)
.has_value()返回对象是否具有值
.type()返回当前类型为std::type_info对象
any_cast<T>()使用当前值作为类型T的值(如果其他类型除外)
.swap()交换两个any对象的值
  

输入和输入一个any对象、

class Test
{
public:
	Test() :a(0) { cout << "构造" << endl; }
	Test(const Test& rhs) :a(rhs.a) { cout << "拷贝" << endl; }
	~Test() { cout << "析构" << endl; }
	friend ostream& operator <<(ostream& os, const Test& rhs)
	{
		return os << rhs.a;
	}
	friend istream& operator>>(istream& is, Test& rhs)
	{
		return is >> rhs.a;
	}
private:
	int a = 0;
};


any s = Test();
cout << any_cast<const Test&>(s) << endl;   //0
cin >> any_cast<Test&>(s);    //输入10
cout << any_cast<const Test&>(s) << endl;  //10,还是同一个对象

总结、

为一个 any 赋值时使用 emplace,销毁值时使用 reset,返回值的类型使用 type。

   std: any也支持移动语义。但是,请注意,move语义必须满足包含的类型具有可复制构造函数。也就是说,不支持只移动类型作为包含值类型。处理move语义的最佳方法可能并不明显。

虽然说我现在不知道这个 any 容器到底有什么用,但是先了解吧。。。。。。