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:
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; }
};
This has no vtable and is what I want
int main() {
Derived d;
d.thisMustBeDefined();
}
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();
}
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, i.e. 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.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With