如何查找多态基类向量中包含的对象的类型?

how to find type of an object contained in a vector of polymorphic base class?

本文关键字:包含 对象 类型 向量 基类 何查找 查找 多态      更新时间:2023-10-16

假设我有一个超级多态基类 Shape,其中许多其他形状类都是从它派生的。

现在,如果我有一个 Shape 指针向量,其中包含指向不同形状类型列表的指针,如下所示:

vector<Shape*> p;  // p contains pointer to many different shape objects

我知道要访问向量 p 中每个形状的方法和成员,我需要使用 dynamic_cast。

但是,如果我不知道向量 p 在运行时实际包含什么怎么办? 如何在运行时安全地找到向量 P 中包含的对象的类型?

我也知道我可以检查 dynamic_cast 的强制转换是否返回 NULL 以获得成功。 但这是否意味着要在向量 p 中找到我的形状对象的实际类型 我必须做这样的事情:

if (dynamic_cast<Circle*> p[i] !=NULL){
// do stuff
}
else if (...) {

}

并对所有其他形状类型重复此模式?

但是如果我有 100 种可能的形状,这就会变得很麻烦。 在朗姆泰姆有更好的方法来实现这一目标吗?

PS-考虑以下场景:

假设我需要遍历 Shape* 向量,例如将所有圆形对象放在一个单独的向量和向量中......现在我需要知道对象的实际类型。检查许多形状的 typeid 和 dynamic_casts 的返回是不切实际的。

您可以在typeinfo标头中使用typeid

例如,请参阅以下问题:如何在运行时确定C++的实际对象类型;

然而,实际的问题是"为什么你需要知道对象的实际类型?":这是AFAIK不需要那么频繁需要这样的功能,因为多态性已经允许管理绝大多数用例。

我知道可以访问矢量中每个形状的方法和成员 p,我需要使用dynamic_cast。

不,不一定! 在您的情况下,假设Shape有一个area方法,在CircleRectangle(它们都扩展了Shape类)中(重新)定义,也许以下内容就足够了:

std::vector<Shape*> shapes;
Rectangle rect(...);
Circle circle(...);
shapes.push_back( &rect );
shapes.push_back( &circle );
shapes[0]->area(); // --> calls Rectangle::area()
shapes[1]->area(); // --> calls Circle::area()

我想出了我并不真正自豪的解决方案,但也许它将有助于创建更好的解决方案。 我试图实现的关键是摆脱明确的dynamic_cast并让这个工作。不过,仍然需要两次命名您的 derieved 类型。 此外,它使用被告知很慢的std::function。需要C++14。 我相信有一种方法可以通过巧妙地使用模板来做到这一点。或者至少摆脱type_switch<A>::cast<B>车道。无论如何,代码:

#include <iostream>
#include <functional>
#include <typeindex>
#include <unordered_map>
// Basic inheritance cases
struct A
{
virtual void foo() = 0;
};
struct B : public A
{
void foo() override { }
void bfoo() {
std::cout << "B specificn";
}
};
struct C : public A
{
void foo() override  { }
};
template <typename T>
struct type_switch
{
using Func = std::function<void(T&)>;
using Pair = std::pair<std::type_index, Func>;
using Map = std::unordered_map<std::type_index, Func>;
Map map;
type_switch(std::initializer_list<Pair> l) : map(l.begin(),l.end())
{
}
void call(T& a)
{
map[typeid(a)](a);
}
// allows for "oneliner", without explicit 'call', but it could end in creation of 
// new type_switch on every loop iteration etc.
type_switch(T&a, std::initializer_list<Pair> l) : type_switch(l){
call(a);
}
template <typename T2>
static Func cast(std::function<void(T2&)> f)
{
static_assert(std::is_base_of<T, T2>::value, "Invalid cast operation on functors, T2 is not base of T");
// lot of functor copyings...
return[f = std::move(f)](T& t) {
f(static_cast<T2&>(t));
};
}
};

int main()
{   
B b;
C c;
int local = 0;
type_switch<A> sw = { 
{ typeid(B), type_switch<A>::cast<B>( [&local](auto& a) { // auto will deduce into B! No explicit casting
std::cout << "Handle b, local value is " << local << 'n';
a.bfoo(); // B specific
local++; // some outer scode operation
}) } ,
{ typeid(C), type_switch<A>::cast<C>([&local](auto& a) {  // auto will deduce into C! No explicit casting
std::cout << "Handle c, local value is " << local << 'n';
local++; // some outer scode operation
})
},
/*  // this one would trigger static_assert
{ typeid(int), type_switch<A>::cast<int>([&local](auto& a) {  // auto will deduce into C! No explicit casting
std::cout << "Handle int, local value is " << local << 'n';
local++; // some outer scode operation
})
},*/
};
sw.call(b);
sw.call(c);
return 0;
}