【问题标题】:Abstract class as an interface, without the vtable抽象类作为接口,没有 vtable
【发布时间】:2017-03-09 00:51:21
【问题描述】:

我想创建一个定义类的一些方法的抽象类。其中一些应该由基类(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

这没有 vtable,这是我想要的

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

是 vtable 1

由于我的编码草率,我错误地强制编译器使用多态性,因此需要一个 vtable。我怎样才能让这种情况引发错误?

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

是 vtable 2

这里我在任何时候都没有提到“Base”类,所以编译器应该知道所有方法都是在编译时预先确定的。但是它仍然会创建一个 vtable。这是我希望能够通过编译错误检测到这一点的另一个例子。

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

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

【问题讨论】:

  • 你错了——一旦你使用了 virtual 这个词,你就得到了该类的任何对象的 vtable;或任何由此而来的东西
  • 恐怕我不是:我已经查看了所有三种情况的生成目标代码
  • 如果您从不使用Base,为什么它存在?确保没有生成支持多态的代码的方法是没有多态。
  • 我会看看en.wikipedia.org/wiki/Curiously_recurring_template_pattern。这将能够实现“静态多态性”。
  • @MartinBonner “你可能不得不放弃强迫自己编写特定的方法。”不一定。您可以将静态检查添加到基类中以确保它们在派生类中的存在。

标签: c++ c++11 inheritance interface abstract-class


【解决方案1】:

正如 cmets 中已经提到的,您可以使用 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 中实现,要使编译器错误更具可读性,您可以使用this answer 中提供的简单静态检查:

#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 模板类派生自的另一个基类将有助于解决这个问题。

【讨论】:

  • 您应该使用int(T::*)(void) 而不是int(T::*)(void) 以使支票有效。使用 is_detected 元函数和表达式 sfinae 可以避免此错误,并且不需要宏。
  • @GuillaumeRacicot THX 进行修复。
  • 老实说,theat 看起来很糟糕,更像是 C 而不是 C++ 代码。它还禁止 C++ 通常用于(并且需要 vtables)的功能。有时最好是结果并使用正确的语言来解决问题。在这里,OP 从一开始就使用 C 会更好。这并不意味着不使用 OOP,而只是意味着您可以完全控制发生的事情并且没有隐藏的开销。
  • @Olaf 我不知道为什么 OP 应该使用 C,尤其是当他想做这样的抽象时。
  • 模板化基类从非模板化基类继承会有帮助吗?在避免重复代码方面。
猜你喜欢
  • 2017-01-16
  • 2021-04-03
  • 2014-08-27
  • 2011-05-22
  • 2013-03-25
  • 1970-01-01
  • 2017-02-19
  • 1970-01-01
  • 2011-01-23
相关资源
最近更新 更多