抽象类作为接口,没有虚函数表

问题描述 投票:0回答:2

我想创建一个抽象类来定义类的一些方法。其中一些应该由基类(Base)实现,一些应该在 Base 中定义但被 Derived 覆盖,另一些应该是 Base 中的纯虚拟以强制在 Derived 中定义。

这当然就是抽象类的用途。但是,我的应用程序只会直接使用 Derived 对象。因此,编译器应该在编译时确切地知道要使用哪些方法。

现在,因为此代码将在 RAM 非常有限的微控制器上运行,所以我热衷于避免实际使用带有 vtable 的虚拟类。从我的测试来看,编译器似乎足够聪明,除非必须,否则不会创建 vtable,至少在某些情况下是这样。然而我被告知永远不要相信编译器:是否可以将此作为编译的必需条件?

以下是一些代码示例:

课程

class Base {
  public:
    Base() {}
    virtual ~Base() {};

    virtual int thisMustBeDefined() = 0;
    virtual int thisCouldBeOverwritten() { return 10; }
    int thisWillBeUsedAsIs() { return 999; }
};

class Derived : public Base {
  public:
    Derived() {}
    ~Derived() {}

    int thisMustBeDefined() { return 11; }

};

没有虚拟表

这没有 vtable,这就是我想要的

int main() {
  Derived d;
  d.thisMustBeDefined();
}

是的 vtable 1

由于我的草率编码,我错误地强制编译器使用多态性,因此需要一个虚函数表。我怎样才能让这个案例抛出错误?

int main() {
  Base * d;
  d = new Derived();
  d->thisMustBeDefined();
}

是的 vtable 2

这里我在任何时候都没有引用类“Base”,因此编译器应该知道所有方法都是在编译时预先确定的。然而它仍然创建一个虚函数表。这是为什么我希望能够通过编译错误检测到这一点的另一个例子。

int main() {
  Derived * d;
  d = new Derived();
  d->thisMustBeDefined();
}

换句话说,如果我编写的代码导致编译器为我的类生成 vtable,即使用多态性,我希望它成为编译器错误。

c++ c++11 inheritance interface abstract-class
2个回答
9
投票

正如评论中已经提到的,您可以使用 CRTP (又名静态多态性)来避免创建 vtable:

template <typename Der>
class Base {
  public:
    Base() {}
    ~Base() {};

    int thisMustBeDefined() {
        // Will fail to compile if not declared in Der
        static_cast<Der*>(this)->thisMustBeDefined();
    }
    int thisCouldBeOverwritten() { return 10; }
    int thisWillBeUsedAsIs() { return 999; }
};

class Derived : public Base<Derived> {
  public:
    Derived() {}
    ~Derived() {}

    int thisMustBeDefined() { return 11; }

    // Works since you call Derived directly from main()
    int thisCouldBeOverwritten() { return 20; }

};

如果某个函数未在

Derived
中实现,为了使编译器错误更具可读性,您可以使用简单的静态检查,如此答案中提供的:

#define DEFINE_HAS_SIGNATURE(traitsName, funcName, signature)               \
    template <typename U>                                                   \
    class traitsName                                                        \
    {                                                                       \
    private:                                                                \
        template<typename T, T> struct helper;                              \
        template<typename T>                                                \
        static std::uint8_t check(helper<signature, &funcName>*);           \
        template<typename T> static std::uint16_t check(...);               \
    public:                                                                 \
        static                                                              \
        constexpr bool value = sizeof(check<U>(0)) == sizeof(std::uint8_t); \
    }

DEFINE_HAS_SIGNATURE(thisMustBeDefined, T::thisMustBeDefined, int(*)(void));

并将静态检查添加到

Base
构造函数中:

Base() {
    static_assert(thisMustBeDefined<Der>::thisMustBeDefined, 
                  "Derived class must implement thisMustBeDefined");
}

虽然在小型设备上工作时应该考虑的一个缺点,并且一次有多个版本的

Derived
Base
中的代码将为每个
Derived
实例重复。

因此,您必须决定对于您的用例来说更重要的限制是什么。

正如 @ChrisDrew 在他们的 comment 中指出的那样,将

thisCouldBeOverwritten()
thisWillBeUsedAsIs()
函数移动到
Base
模板类派生的另一个基类将有助于解决这个问题。


0
投票

正如 πάντα ῥεῖ 的回答中提到的,CRTP 就是这里的解决方案。 但更正一下: 方法

thisMustBeDefined
实际上并不要求 Derived 实现它。

int thisMustBeDefined() {
    // Incorrect theory: Will fail to compile if not declared in Der
    static_cast<Der*>(this)->thisMustBeDefined();
}

参见 godbolt https://godbolt.org/z/x6o1T6jn1

您可以使用

static_assert(false)
来要求在派生类中定义函数,但请注意,它仅在程序中使用该函数时才有效。

template <typename Der>
class Base {
  public:
    int thisMustBeDefined() {
        static_assert(false); // Derived class must define this function
    }
};

class Derived : public Base<Derived> {
  public:
    // int thisMustBeDefined() { return 11; } // LINE A
};

int main() {
    Derived d;
    d.thisMustBeDefined(); // LINE B
}

// Compile error will occur if (LINE A does not exist) && (LINE B exists),

参见 godbolt https://godbolt.org/z/sPona3aeY

© www.soinside.com 2019 - 2024. All rights reserved.