【问题标题】:Know the class of a subclass in C++了解 C++ 中子类的类
【发布时间】:2013-07-14 18:34:24
【问题描述】:

我至少有 7 年没有接触过 C++ 了,突然间我深深地投入到了一个 C++ 项目中。我想要一些关于以下方面的指导:

我有一个名为 Animal 的类,我有 3 个继承自 Animal 的类:Cat、Dog 和 Bird。我创建了一个列表对象并使用它来存储动物类型。

此列表可以包含 Cats Dogs 和 Birds,当我遍历此 Animals 列表时,我想知道每个 Animal 的直接类型(无论是 Cat、Dog 还是 Bird)。

当我说typeid(animal).name(); 时,它给了我动物,这是真的,但我想知道是什么样的动物。

有什么想法吗??我应该使用枚举吗??

【问题讨论】:

  • 如果可能的话,不要尝试这样做。创建 Animal 抽象接口,以便具体类型无关紧要。
  • 如果你的列表是 std::list 你不会得到多态性,你需要使用智能指针
  • @MarkB 把它写在答案中,这样我就可以投票了
  • 您有动物列表或列出动物的指针? (顺便说一句,在精心设计的界面中,您不需要知道什么样的动物是实例。)
  • 同意马克。但更根本的是,听起来您正在存储Animal 而不是Animal * 的列表。一旦将 Bird 存储到 Animal 实例中,它就不再是 Bird,而只是 Animal。

标签: c++ oop inheritance polymorphism


【解决方案1】:

你几乎肯定不想知道。您应该做的是声明为与这些动物交互的虚拟适当方法。

如果您需要专门对它们进行操作,您可以使用访问者模式来传递访问者对象或在每个具体类中处理正确的数据。如果您坚持使用标签(我强调这是第三种选择 - 其他两种解决方案将使您的代码更清晰),请使用名为 classname 的虚拟方法返回类型标识符(无论是作为字符串还是 int 或任何)。

如果你有一个对象类型的数组,而不是指针类型,还要注意关于切片的要点。如果你 7 年没有使用过 C++,你可能没有意识到模板使用的增长使语言变得更好。查看 boost 之类的库,看看可以做什么,以及模板如何让您编写类型推断的通用代码。

【讨论】:

  • 我知道真正的多态性意味着不需要知道具体的类,但出于其他目的,最好知道,我喜欢你的虚拟类型名称方法,这可能满足我的需要。
  • 虽然我完全同意你的回答,但我仍然要指出 typename 是 C++ 中的保留关键字。
  • @Aesthete 这是 2002 年以来的新功能吗?这是我使用的最后一个标准。
  • @Marcin,没有 2002 标准。是98、03、11,是03的关键词,98我不太清楚。
  • @Marcin 一直都在。
【解决方案2】:
Dog* dog = dynamic_cast<Dog*>(myAnimalPointer);
if (dog == NULL)
{ 
    // This animal is not a dog.
}

【讨论】:

  • 这是迄今为止对这个问题的唯一实际“答案”,我想说。虽然虚函数的想法也有效。 +1
  • 或者不可抗拒的流线型...if (Dog* dog = dynamic_cast&lt;Dog*&gt;(myAnimalPointer)) ...。但是,一般来说,这不一定是 正确 答案 - 如果以后有进一步派生的 DobermanPoodle 类,则此代码将继续编译和运行,而无需告知最派生的名称.好的,尽管问题中的层次结构非常有限。
  • @RickyMutschlechner,尽管这是对这个问题的直接答案,但大多数其他人都意识到 OP 提出了错误的问题。虽然 dynamic_casting 对于 Capability Query 习语很有用,但该特定习语似乎与所提出的问题无关。
  • 如果所有类都没有虚拟方法,这将不起作用。如果情况并非如此,并且没有 oop 方法可以避免它,那么这就是要走的路。
【解决方案3】:

需要知道特定的具体对象类型通常是 C++ 中的一种设计味道,所以我建议您不要尝试这样做。

相反,在Animal 中创建一个抽象(纯虚拟)接口,描述您希望您的动物拥有的功能。然后,您可以利用动态调度来调用该功能,甚至不需要知道对象的动态类型。如果需要,您始终可以在子类中创建私有的非虚拟辅助函数。

还请注意,您需要通过(智能)指针将Animals 存储在容器中,而不是按值存储。如果您按值存储它们,它们将在插入列表时全部被切片,从而丢失动态类型信息。

正如@Marcin 指出的那样,如果您确实需要在特定子类上调用特定方法,则使用访问者模式进行双重调度可能是一种更好的方法。

【讨论】:

    【解决方案4】:

    根据具体代码typeid 返回不同的东西。另外name() 可以返回任何内容(包括将首字母变为大写或删除 *),它仅用于调试。现在我有几个不同的可能答案typeid(animal).name() 可以返回。

    版本 1 animal 是一个类名:

    struct animal {
        virtual ~animal() {}
    };
    
    struct dog 
        : animal
    {};
    
    struct cat
        : animal
    {};
    
    struct bird
        : animal
    {};
    
    int main() {
        std::cout << typeid(animal).name() << std::endl; // animal
        return 0;
    }
    

    版本 2 animalAnimal 的 typedef:

    struct Animal {
    };
    
    struct Dog 
        : Animal
    {};
    
    struct Cat
        : Animal
    {};
    
    struct Bird
        : Animal
    {};
    
    int main() {
        typedef Animal animal;
        std::cout << typeid(animal).name() << std::endl; // Animal
        return 0;
    }
    

    Vesion 3 animal 是一个指针:

    struct Animal {
    };
    
    struct Dog 
        : Animal
    {};
    
    struct Cat
        : Animal
    {};
    
    struct Bird
        : Animal
    {};
    
    int main() {
        Dog d;
        Animal* animal=&d;
        std::cout << typeid(animal).name() << std::endl; // Animal*
        return 0;
    }
    

    版本 4 animal 是一个对象:

    struct Animal {
    };
    
    struct Dog 
        : Animal
    {};
    
    struct Cat
        : Animal
    {};
    
    struct Bird
        : Animal
    {};
    
    int main() {
        Animal animal;
        std::cout << typeid(animal).name() << std::endl; // Animal
        return 0;
    }
    

    版本 6 animal 是对非多态对象的引用:

    struct Animal {
    };
    
    struct Dog 
        : Animal
    {};
    
    struct Cat
        : Animal
    {};
    
    struct Bird
        : Animal
    {};
    
    int main() {
        Dog d;
        Animal& animal=d;
        std::cout << typeid(animal).name() << std::endl; // Animal
        return 0;
    }
    

    版本 7 animal 是对多态对象的引用:

    struct Animal {
      ~virtual Animal() {}
    };
    
    struct Dog 
        : Animal
    {};
    
    struct Cat
        : Animal
    {};
    
    struct Bird
        : Animal
    {};
    
    int main() {
        Dog d;
        Animal& animal=d;
        std::cout << typeid(animal).name() << std::endl; //Dog
        return 0;
    }
    

    正如其他人所写,最好不要依赖name()。但是如果没有一些代码,就很难说什么是正确的。

    【讨论】:

    • 您在版本 6 和 7 中的代码是相同的。有什么遗漏吗?
    • @DarrelHoffman 哦,谢谢我的错误。我忘记了我添加的虚拟析构函数。将代码传输到 SO 时出现复制编辑错误。
    【解决方案5】:

    由于列表可以包含任何类型的动物,我将假设它是一个指针列表。 In such case typeid will consider the most derived type of the object if you pass it the dereferenced pointer.

    typeid(*animal).name();
    

    就是你要找的。​​p>

    【讨论】:

    • 可能有效,但name() 的文本内容是实现定义的,允许为空,因此不可靠/不便携。
    【解决方案6】:

    在每个子类中实现一个函数 name()。

    【讨论】:

    • 一个虚函数??我必须能够从我的 Animal 对象中调用这个函数。
    • 是的,但不要认为这是最好的方法。名称是一个字符串,你必须在其中进行字符串比较...
    • 这就是为什么我在考虑枚举,但不知道如何让它与子类一起工作。
    • 很容易,只需将枚举放入 Animal 类中,并让重载的虚拟返回正确的值。但是这个方法的问题是现在 Base 类需要知道所有类型的动物,这是不好的 juju。因为基类应该尽可能少地了解派生自它的事物。它违反了 DRY。
    • @PaulG 有两种方法。第一种方法:在基类中为枚举创建一个字段并在所有构造函数中设置它。第二种方法是创建一个虚函数,返回正确的枚举值,并在所有子类中覆盖它以获得正确的值。
    【解决方案7】:

    如果不使用特殊技巧为您的基类提供有关派生类型的信息,它就无法知道实例是什么子类型。正如@Joachim Wuttke 建议的那样,最简单的方法是创建一个虚拟函数,强制派生类实现 name() 方法。

    但是,如果您想变得更高级一点,奇怪的重复出现的模板部分 CRTP 提供了一个更优雅但深奥的解决方案:

    #include <typeinfo>
    #include <string>
    #include <iostream>
    
    
    template <class T>
    class Animal {
    public:
        virtual ~Animal() {};  // a base class
        std::string name() {
            return typeid(T).name();
        }
    };
    
    
    class Cat: public Animal<Cat> {
    
    };
    
    class Dog: public Animal<Dog> {
    
    };
    
    int main( int argc, char* argv[] ){
        Cat c;
        Dog d;
    
        std::cout << c.name() << std::endl;
        std::cout << d.name() << std::endl;
    
    }
    

    结果(g++):

    3Cat
    3Dog
    

    结果(vs2008):

    class Cat
    class Dog
    

    请注意,正如其他人所说,typeid 的名称修饰依赖于平台/编译器,因此要从上面的名称返回到类,您必须实现一个依赖于平台/编译器的去修饰例程。不是特别困难,但它确实使解决方案失去了优雅。

    【讨论】:

      猜你喜欢
      • 2017-10-10
      • 2015-11-08
      • 1970-01-01
      • 2013-04-10
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2011-09-27
      • 2016-06-16
      相关资源
      最近更新 更多