简体   繁体   中英

Abstract class as an interface, without the vtable

I'd like to make an abstract class that defines some methods of a class. Some of these should be implemented by the base class (Base), some should be defined in Base but overwritten by Derived and others should be pure virtual in Base to force definition in Derived.

This is of course what abstract classes are for. However, my application will only ever use the Derived object directly. Because of this, the compiler should know at compile-time exactly which methods are to be used.

Now, because this code will run on a microcontroller with very limited RAM, I'm keen to avoid actually using a virtual class with the vtable this entails. From my testing it seems that the compiler is smart enough to not make a vtable unless it has to, at least in some circumstances. However I've been told to never trust the compiler: is it possible to make this a required condition of compilation?

Here are some code examples:

Classes

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; }

};

No vtable

This has no vtable and is what I want

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

Yes vtable 1

As a result of my sloppy coding, I've mistakenly forced the compiler to use polymorphism and therefore to require a vtable. How can I make this case throw an error?

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

Yes vtable 2

Here I've not refered to the class "Base" at any point, so the compiler should know that all the methods are pre-determined at compile time. However it still creates a vtable. This is another example of why I want to be able to detect this with a compile error.

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

In other words, I want it to be a compiler error if I write code that results in the compiler producing a vtable for my classes, ie uses polymorphism.

As it was already mentioned in the comments you can use the CRTP (aka static polymorphism) to avoid creation of a 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; }

};

To make compiler errors more readable if a function is not implemented in Derived you can use a simple static check like provided in 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));

and add the static check to the Base constructor:

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

Though one drawback you should consider when working on a small device, and you have more versions of Derived at a time is that the code in Base will be duplicated for each Derived instance.

So you have to decide if what's the more important limitation for your use case.

As @ChrisDrew pointed out in their comment , moving the thisCouldBeOverwritten() and thisWillBeUsedAsIs() functions to another base class that the Base template class derives from would facilitate that problem.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM