Generating an Interface Without Virtual Functions

C++ interface without virtual functions

It appears that you want to implement concepts (lite). You may want to read the article before attempting an implementation.

Absent compiler support, you can partially implement this idea. Your static_assert idea is a known way to express interface requirements.

Consider the Sortable example from the link. You can create a class template Sortable, use static_assert to assert all kind of thinks about the template parameter. You explain to your users that they need to implement a certain cet of methods, and to enforce that set is implemented, they need to make use of Sortable<TheirClass> one way or another.

In order to express, right in a function declaration. the idea that your function requires a Sortable, you will have to resort to something like this:

template <typename Container> 
auto doSomethingWithSortable (Container&) -> std::enable_if<Implements<Container, Sortable>>::type;

Generating an interface without virtual functions?

You can use free functions to model the drawable aspect of your objects:

#include <iostream>

class Image { };
class Sprite { };
class Model3D { };

namespace draw_aspect
{
void draw(Image const& image) { std::cout << "drawing image\n"; }
void draw(Sprite const& sprite) { std::cout << "drawing sprite\n"; }
void draw(Model3D const& model3D) { std::cout << "drawing model3D\n"; }
}

Now, either use three separate vectors (this could well be most optimal, depending on the ordering relationship between the objects across collections?), or consider a variant type vector:

1. Using variant types

#include <boost/variant.hpp>
using SceneObject = boost::variant<Image, Sprite, Model3D>;

namespace draw_aspect {

struct draw_visitor : boost::static_visitor<> {
template <typename T> void operator()(T const& t) const { draw(t); }
};

void draw(SceneObject const& sobj) {
static const draw_visitor _vis;
boost::apply_visitor(_vis, sobj);
}
}

A complete proof of concept of the latter: Live on Coliru

#include <vector>

class SceneManager //controls everything in the "world" game
{
public:
void Add(SceneObject v) { _worldObjects.emplace_back(std::move(v)); }
friend void draw(SceneManager const& sm) { return sm.draw(); }
private:
void draw() const {
for(auto& sobj : _worldObjects)
draw_aspect::draw(sobj);
}
std::vector<SceneObject> _worldObjects; //the vector that contains all of them
};

int main()
{
SceneManager sman;

sman.Add(Image());
sman.Add(Sprite());
sman.Add(Model3D());
sman.Add(Image());

draw(sman);
}

Outputs

drawing image
drawing sprite
drawing model3D
drawing image

2. Separate collections

The alternative using separate vectors: Live on Coliru

class SceneManager //controls everything in the "world" game
{
public:
void Add(Image v) { _images .emplace_back(std::move(v)); }
void Add(Sprite v) { _sprites .emplace_back(std::move(v)); }
void Add(Model3D v) { _model3Ds.emplace_back(std::move(v)); }

friend void draw(SceneManager const& sm) { return sm.draw(); }
private:
void draw() const {
for(auto& sobj : _images) draw_aspect::draw(sobj);
for(auto& sobj : _sprites) draw_aspect::draw(sobj);
for(auto& sobj : _model3Ds) draw_aspect::draw(sobj);
}
std::vector<Image> _images;
std::vector<Sprite> _sprites;
std::vector<Model3D> _model3Ds;
};

int main()
{
SceneManager sman;

sman.Add(Image());
sman.Add(Sprite());
sman.Add(Model3D());
sman.Add(Image());

draw(sman);
}

Note that the output is different (ordering):

drawing image
drawing image
drawing sprite
drawing model3D

Statically-enforcing a C++ class interface without using virtual methods

If you need to enforce your programmers to hide a base class member function by implementing a function of the same name in the derived class.... then leave the base class member function undefined. Attempts to call it will result in linker errors and build failure.

If your users don't try to call the function then the build will still work, which is not necessarily true of virtual functions. But do you care about that?

struct Parent
{
void mustDefine();
};

struct Child : Parent
{
// Link failure if this is missing, due to call in main().
// That's because, without Child::mustDefine() hiding
// Parent::mustDefine(), lookup for `mustDefine` will find
// Parent::mustDefine() instead... which is, well, undefined.
void mustDefine() {}
};

int main()
{
Child c;
c.mustDefine();
}

Abstract class as an interface, without the vtable

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.

How do you declare an interface in C++?

To expand on the answer by bradtgmurray, you may want to make one exception to the pure virtual method list of your interface by adding a virtual destructor. This allows you to pass pointer ownership to another party without exposing the concrete derived class. The destructor doesn't have to do anything, because the interface doesn't have any concrete members. It might seem contradictory to define a function as both virtual and inline, but trust me - it isn't.

class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};

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

class Child : public Parent, public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};

You don't have to include a body for the virtual destructor - it turns out some compilers have trouble optimizing an empty destructor and you're better off using the default.

Is it possible to create an interface without using dynamic dispatch?

You have only one class, so there is no polymorphism by definition. You don't need inheritance, interfaces, CRTP, or any such hacks.

class SHTHumiditySensor /* no ': public HumiditySensor' needed */ {
private:
SHTSensor &sht;
public:
explicit SHTHumiditySensor(SHTSensor *sht);
void readSample() /* no virtual, no override */;
double getHumidity() const /* no virtual, no override */;
double getTemperature() const /* no virtual, no override */;
};

using HumiditySensor = SHTHumiditySensor;

No Virtual Function Table for abstract class?

These "interfaces" abstract classes probably have need no user written code in any their constructors and destructors (these either have an empty body and no ctor-init-list, or simply are never user defined); let's call these pure interface classes.

[Pure interface class: concept related but not identical to Java interfaces that are (were?) defined as having zero implementation code, in any member function. But be careful with analogies, as Java interfaces inheritance semantic isn't the same as C++ abstract classes inheritance semantic.]

It means that in practice no used object ever has pure interface class type: no expression ever refers to an object with pure interface type. Hence, no vtable is ever needed, so the vtable, which may have been generated during compilation, isn't included in linked code (the linker can see the symbol of the pure interface class vtable isn't used).



Related Topics



Leave a reply



Submit