首页 > 解决方案 > 静态成员重载和覆盖如何在 C++ 结构中工作?

问题描述

这是我最近在虚幻引擎中看到的一个示例代码。

用例是层次结构中的给定结构将其“类 id”在层次结构中作为静态整数字段。

使用辅助函数作为成员,通过比较 ClassID 字段来识别结构的“类型”。

这些结构旨在始终存储为对象变量,而不是指针

struct ENGINE_API FDamageEvent
{

static const int32 ClassID = 0;

    virtual int32 GetTypeID() const { return FDamageEvent::ClassID; }
    virtual bool IsOfType(int32 InID) const { return FDamageEvent::ClassID == InID; };

}

struct ENGINE_API FPointDamageEvent : public FDamageEvent
{

    /** ID for this class. NOTE this must be unique for all damage events. */
    static const int32 ClassID = 1;
    virtual int32 GetTypeID() const override { return FPointDamageEvent::ClassID; };
    virtual bool IsOfType(int32 InID) const override { return (FPointDamageEvent::ClassID == InID) || FDamageEvent::IsOfType(InID); };

}

struct ENGINE_API FRadialDamageEvent : public FDamageEvent
{
    /** ID for this class. NOTE this must be unique for all damage events. */
    static const int32 ClassID = 2;

    virtual int32 GetTypeID() const override { return FRadialDamageEvent::ClassID; };
    virtual bool IsOfType(int32 InID) const override { return (FRadialDamageEvent::ClassID == InID) || FDamageEvent::IsOfType(InID); };

}

所以这里的问题是

  1. 是否ClassID在子结构中重新分配、重新声明或隐藏字段?
  2. 为什么GetTypeID函数在检索时使用自身的显式范围, ClassID尽管它是虚拟的?
  3. 为什么IsOfType子结构中的函数调用父类的IsOfType函数?是否只是为了模拟每个孩子也是其父类型的多态性?

标签: c++unreal-engine4

解决方案


根据 c++ 标准[class.derived.2]

除非在派生类中重新声明,否则基类的成员也被视为派生类的成员。

这意味着如果您不在派生类中重新声明classId当您在派生类中访问它时,您实际上是在使用基类之一。

但是,在您的情况下,您确实重新声明了classId. 在这种情况下,classIdofbaseClass 不被视为派生类的成员,因此classId在派生类范围内访问(没有解析限定符FDamageEvent::)将引用classId您在派生类中声明的内容。

你可以把这个名字叫做shadowing(overloading让人想到函数)。

但请记住:静态变量从不涉及多态性!(除非你正在做静态多态性,那么那将是另一回事)。多态性依赖于实例,静态成员不与任何实例相关联.使用实例或->实例(或指向实例的指针)访问静态成员是指实例的编译时类型的静态变量,而不是其多态类型(这两者在 c++ 中是不同的)。考虑以下代码:

struct Base {
    static const int id = 0;
    virtual int get_id() const {
        return id;
    };
};

struct Derived : Base {
    static const int id = 1;
    int get_id() const override {
        return id;
    }
};

int main() {
    Base* b = new Derived;
    std::cout << b->id << std::endl;        // Output: 0
    std::cout << b->get_id() << std::endl;  // Output: 1
    return 0;
}

根据问题 2,此处不需要使用显式范围:classId并且FRadialDamageEvent::classId是等效的。这只是代码品味的问题。为了清楚起见,有些人更喜欢后者。

根据问题 3,我不太了解这段代码的意图。想象一下,你有一个多级继承层次结构,例如A -> B -> CX -> Yreads Y derives from X),然后根据这个方法,一个Cis not的实例B,这违反了传统的面向对象模式。


关于关键字override:它只是一个关键字,以确保您实际上覆盖了基类的虚拟方法。virtual如果使用,则不必编写override,因为后者已经使函数成为虚拟函数。如果该函数实际上不是基类中的虚函数,那么编译器会发出一个错误:这可以防止声明新虚方法的拼写错误,而不是实际覆盖一个虚方法,并且更清晰。


推荐阅读