【问题标题】:Use RTTI to get class name from object implementing interface c++使用 RTTI 从实现接口 c++ 的对象中获取类名
【发布时间】:2021-01-28 12:06:32
【问题描述】:

我在 Animal 命名空间中有一个类 AnimalInterface:

namespace Animal
{
    class AnimalInterface
    {
    public:
        AnimalInterface() = default;
        virtual ~AnimalInterface() = default;

        virtual std::string makeSound() = 0;
    };
}

假设我有 2 个从这个接口实现的类

class Dog : public AnimalInterface
{
public:
    Dog() = default;
    ~Dog() = default;

    std::string makeSound()
    {
        return "Bark";
    }
};

class Cat : public AnimalInterface
{
public:
    Cat() = default;
    ~Cat() = default;

    std::string makeSound()
    {
        return "Meow";
    }
};

现在我有了一个调用这些函数的类。我希望能够使用运行时类型信息从对象中获取动物的类名。

void AnimalSounds
{
    list<unique_ptr<AnimalInterface>> animals;
    animals.push_back(make_unique<Dog>());
    animals.push_back(make_unique<Cat>());

    for (const auto& animal : animals)
    {

        cout << typeid(animal).name() << " makes sound: " << animal->makeSound() << endl;
    }
}

我希望输出是

Dog makes sound: Bark
Cat makes sound: Meow

相反,我得到以下内容

class std::unique_ptr<class Animal::AnimalInterface,struct std::default_delete<class Animal::AnimalInterface> > makes sound: Bark
class std::unique_ptr<class Animal::AnimalInterface,struct std::default_delete<class Animal::AnimalInterface> > makes sound: Meow

我该如何解决这个问题?谢谢!

【问题讨论】:

  • 我希望能够使用反射从对象中获取动物的类名。 -- 目前在 C++ 中没有反射。这看起来像 XY Problem
  • typeid 返回的字符串是实现定义的。 stackoverflow.com/questions/1986418/typeid-versus-typeof-in-c
  • 想想animal是什么类型。真的是Dog 还是Cat?无论如何,您不能依赖typeid(...).name(),它的输出既不能保证是类名,也不能保证是一致的(甚至在同一个程序的执行之间也不行)。
  • 怎么样--std::unordered_map&lt;std::string, std::unique_ptr&lt;AnimalInterface&gt;&gt; 或者动物可以有重复的名字std::unordered_multimap&lt;std::string, std::unique_ptr&lt;AnimalInterface&gt;&gt;

标签: c++ reflection unique-ptr


【解决方案1】:

您需要取消对指针的引用。这样您就可以从它指向的对象而不是指针本身获取 RTTI 信息:

const auto& animal_ref = *animal;
cout << typeid(animal_ref).name() << " makes sound: " << animal->makeSound() << endl;
3Dog makes sound: Bark
3Cat makes sound: Meow

请注意,您仍然必须按照 cmets 中的描述处理实现定义的名称。您可以为此使用库的 demangle 函数:

int status;
const auto& animal_ref = *animal;
const auto name = abi::__cxa_demangle(typeid(animal_ref).name(), 0, 0, &status);
cout << name << " makes sound: " << animal->makeSound() << endl;
Dog makes sound: Bark
Cat makes sound: Meow

除非为了更好地理解 RTTI 或出于调试目的而提出这个问题,否则我仍然强烈建议您重新考虑您的方法。这不是你的代码应该依赖的。

(是的,从技术上讲,即使是拆解也可能不够,并且名称​​可能在调用之间发生变化。在我使用的实现中,这从未发生过。在我们谈论的时候关于猫叫,这更多的是技术问题而不是实际问题。)

【讨论】:

    【解决方案2】:

    1.) animal 是对来自animals 的某个元素的引用。 animals 包含什么?它包含unique_ptr&lt;AnimalInterface&gt;,所以编译器打印出完全正确的东西,只是有点冗长。

    2.) typeif(...).name() 并不是真正的反思。特别是它不会给你任何关于结果的保证。另见reference

    不提供任何保证;特别是,返回的字符串对于多种类型可以是相同的,并且在同一程序的调用之间会发生变化。

    3.) 我只能想到一种健壮且符合标准的方法来实现您想要的:另一个返回动物种类的虚拟函数。或者一些编译器特定的扩展,但标准 C++ 目前不提供任何真正的反射。

    【讨论】:

      【解决方案3】:

      我认为您遇到的意外问题是您的animals 列表包含unique_ptrAnimals。因此,当您遍历该列表时,循环中的每个animal 都是一个unique_ptr&lt;Animal&gt;,它是一个带有默认参数的模板。为了至少解决这个问题,在使用 typeid 之前取消引用指针:

      for (const auto& animal : animals)
      {
        cout << typeid(*animal).name() << " makes sound: " << animal->makeSound() << endl;
      }
      

      但是,您在这里得到的仍然是实现定义的。阅读 RTTI 了解更多信息。

      【讨论】:

        猜你喜欢
        • 2013-01-15
        • 1970-01-01
        • 2015-04-25
        • 1970-01-01
        • 1970-01-01
        • 2013-03-23
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多