Abstract class as an interface, without vtable

I would like to create an abstract class that defines some methods of the class. Some of them must be implemented by the Base class, some of them must be defined in Base but rewritten by Derived, and others must be pure virtual in Base to determine the strength in Derived.

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

Now, since this code will work on a microcontroller with very limited RAM, I really want to avoid using a virtual class with vtable, which this entails. From my testing, it seems that the compiler is smart enough not to do vtable if it is not needed, at least in some cases. However, they told me that they never trust the compiler: is it possible to make this a prerequisite for compilation?

Here are some sample code:

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

It has no vtable and I want

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

Yes vtable 1

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

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

Yes vtable 2

Here I did not refer to the Base class at any point, so the compiler should know that all methods are predefined at compile time. However, it still creates a vtable. This is another example of why I want to detect this with a compilation error.

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

In other words, I want this to be a compiler error if I write code that leads to a compiler producing a vtable for my classes, i.e. uses polymorphism.

+6
source share
1 answer

As mentioned in the comments, you can use CRTP (also known as static polymorphism) to avoid creating 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 the function is not implemented in Derived , you can use a simple static check, as shown 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 static validation to the Base constructor:

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

Although one of the drawbacks should be considered when working with a small device, and you have more versions of Derived at a time, the code in Base will be duplicated for each instance of Derived .

So, you need to decide which more important limitation is for your use case.

As @ChrisDrew pointed out in comment , moving the functions thisCouldBeOverwritten() and thisWillBeUsedAsIs() to another base class that received the Base template class will ease this problem.

+5
source

Source: https://habr.com/ru/post/1011722/


All Articles