Because of my device I can\'t use virtual functions. Suppose I have:
class Base
{
void doSomething() { }
};
class Derived : public Base
{
void doSom
There is simply no simple way to do this without virtual methods.
You can make your own vtable, I suppose. I'd just be a struct containing your "virtual" function pointers as part of Base, and have code to set up the vtable.
This is kind of a gross solution -- it's the C++ compiler's job to handle this feature.
But here goes:
#include <iostream>
class Base
{
protected:
struct vt {
void (*vDoSomething)(void);
} vt;
private:
void doSomethingImpl(void) { std::cout << "Base doSomething" << std::endl; }
public:
void doSomething(void) { (vt.vDoSomething)();}
Base() : vt() { vt.vDoSomething = (void(*)(void)) &Base::doSomethingImpl;}
};
class Derived : public Base
{
public:
void doSomething(void) { std::cout << "Derived doSomething" << std::endl; }
Derived() : Base() { vt.vDoSomething = (void(*)(void)) &Derived::doSomething;}
};
You can use template for compile-time polymorphism.
template<class SomethingDoer> class MyClass
{
public:
void doSomething() {myDoer.doSomething();}
private:
SomethingDoer myDoer;
};
class BaseSomethingDoer
{
public:
void doSomething() { // base implementation }
};
class DerivedSomethingDoer
{
public:
void doSomething() { // derived implementation }
};
typedef MyClass<BaseSomethingDoer> Base;
typedef MyClass<DerivedSomethingDoer> Derived;
Now, we can't point to a Derived
with a Base
pointer, but we can have templated functions that take in a MyClass, and that will work with both Base
and Derived
objects.
You can downcast the object to the Derived type and call it, like so:
static_cast<Derived*>(obj)->doSomething();
though that does not afford any guarantees that what 'obj' points to really is of type Derived
.
I'm more concerned that you don't even have access to virtual functions. How do destructors work if none of your functions can be virtual, and you are subclassing?
My first answer shows that it is indeed possible to get at least a limited form of polymorphic-like behavior without actually relying on the language's support for polymorphism.
However, that example has an enormous amount of boilerplate. It certainly wouldn't scale well: for every class that you add you have to modify six different places in the code, and for every member function that you want to support, you need to duplicate most of that code. Yuck.
Well, good news: with the help of the preprocessor (and the Boost.Preprocessor library, of course), we can easily extract most of that boilderplate and make this solution manageable.
To get the boilerplate out of the way, you'll need these macros. You can put them in a header file and forget about them if you want; they are fairly generic. [Please don't run away after reading this; if you aren't familiar with the Boost.Preprocessor library, it probably looks terrifying :-) After this first code block, we'll see how we can use this to make our application code a lot cleaner. If you want, you can just ignore the details of this code.]
The code is presented in the order it is because if you copy and past each of the code blocks from this post, in order, into a C++ source file, it will (I mean should!) compile and run.
I've called this the "Pseudo-Polymorphic Library;" any names beginning with "PseudoPM," with any capitalization, should be considered reserved by it. Macros beginning with PSEUDOPM
are publicly callable macros; macros beginning with PSEUDOPMX
are for internal use.
#include <boost/preprocessor.hpp>
// [INTERNAL] PSEUDOPM_INIT_VTABLE Support
#define PSEUDOPMX_INIT_VTABLE_ENTRY(r, c, i, fn) \
BOOST_PP_COMMA_IF(BOOST_PP_NOT_EQUAL(0, i)) \
& c :: BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(4, 0, fn), Impl)
// [INTERNAL] PSEUDOPM_DECLARE_VTABLE Support
#define PSEUDOPMX_DECLARE_VTABLE_STRUCT_MEMBER(r, c, i, fn) \
BOOST_PP_TUPLE_ELEM(4, 1, fn) \
(c :: * BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(4, 0, fn), Ptr)) \
BOOST_PP_TUPLE_ELEM(4, 3, fn);
#define PSEUDOPMX_DECLARE_VTABLE_STRUCT(r, memfns, c) \
struct BOOST_PP_CAT(PseudoPMIntVTable, c) \
{ \
friend class c; \
BOOST_PP_SEQ_FOR_EACH_I(PSEUDOPMX_DECLARE_VTABLE_STRUCT_MEMBER, c, memfns)\
};
#define PSEUDOPMX_DECLARE_VTABLE_ENUM_MEMBER(r, x, i, c) \
BOOST_PP_COMMA_IF(BOOST_PP_NOT_EQUAL(0, i)) BOOST_PP_CAT(PseudoPMType, c)
#define PSEUDOPMX_DECLARE_VTABLE_UNION_MEMBER(r, x, c) \
BOOST_PP_CAT(PseudoPMIntVTable, c) BOOST_PP_CAT(BOOST_PP_CAT(table_, c), _);
#define PSEUDOPMX_DECLARE_VTABLE_RESET_FN(r, x, c) \
void Reset(BOOST_PP_CAT(PseudoPMIntVTable, c) table) \
{ \
type_ = BOOST_PP_CAT(PseudoPMType, c); \
table_.BOOST_PP_CAT(BOOST_PP_CAT(table_, c), _) = table; \
}
#define PSEUDOPMX_DECLARE_VTABLE_PUBLIC_FN(r, x, fn) \
BOOST_PP_TUPLE_ELEM(4, 1, fn) \
BOOST_PP_TUPLE_ELEM(4, 0, fn) \
BOOST_PP_TUPLE_ELEM(4, 3, fn);
// [INTERNAL] PSEUDOPM_DEFINE_VTABLE Support
#define PSEUDOPMX_DEFINE_VTABLE_ARGLIST0
#define PSEUDOPMX_DEFINE_VTABLE_ARGLIST1 a0
#define PSEUDOPMX_DEFINE_VTABLE_ARGLIST2 a0, a1
#define PSEUDOPMX_DEFINE_VTABLE_ARGLIST3 a0, a1, a2
#define PSEUDOPMX_DEFINE_VTABLE_ARGLIST4 a0, a1, a2, a3
#define PSEUDOPMX_DEFINE_VTABLE_ARGLIST5 a0, a1, a2, a3, a4
#define PSEUDOPMX_DEFINE_VTABLE_ARGLIST6 a0, a1, a2, a3, a4, a5
#define PSEUDOPMX_DEFINE_VTABLE_ARGLIST7 a0, a1, a2, a3, a4, a5, a6
#define PSEUDOPMX_DEFINE_VTABLE_ARGLIST8 a0, a1, a2, a3, a4, a5, a6, a7
#define PSEUDOPMX_DEFINE_VTABLE_ARGLIST9 a0, a1, a2, a3, a4, a5, a6, a7, a8
#define PSEUDOPMX_DEFINE_VTABLE_FNP(r, x, i, t) \
BOOST_PP_COMMA_IF(BOOST_PP_NOT_EQUAL(0, i)) \
t BOOST_PP_CAT(a, i)
#define PSEUDOPMX_DEFINE_VTABLE_FN_CASE(r, fn, i, c) \
case BOOST_PP_CAT(PseudoPMType, c) : return \
( \
static_cast<c*>(this)->*pseudopm_vtable_.table_. \
BOOST_PP_CAT(BOOST_PP_CAT(table_, c), _). \
BOOST_PP_CAT(BOOST_PP_TUPLE_ELEM(4, 0, fn), Ptr) \
)( \
BOOST_PP_CAT( \
PSEUDOPMX_DEFINE_VTABLE_ARGLIST, \
BOOST_PP_TUPLE_ELEM(4, 2, fn) \
) \
);
#define PSEUDOPMX_DEFINE_VTABLE_FN(r, classes, fn) \
BOOST_PP_TUPLE_ELEM(4, 1, fn) \
BOOST_PP_SEQ_HEAD(classes) :: BOOST_PP_TUPLE_ELEM(4, 0, fn) \
( \
BOOST_PP_SEQ_FOR_EACH_I( \
PSEUDOPMX_DEFINE_VTABLE_FNP, x, \
BOOST_PP_TUPLE_TO_SEQ( \
BOOST_PP_TUPLE_ELEM(4, 2, fn), \
BOOST_PP_TUPLE_ELEM(4, 3, fn) \
) \
) \
) \
{ \
switch (pseudopm_vtable_.type_) \
{ \
BOOST_PP_SEQ_FOR_EACH_I(PSEUDOPMX_DEFINE_VTABLE_FN_CASE, fn, classes) \
} \
}
// Each class in the classes sequence should call this macro at the very
// beginning of its constructor. 'c' is the name of the class for which
// to initialize the vtable, and 'memfns' is the member function sequence.
#define PSEUDOPM_INIT_VTABLE(c, memfns) \
BOOST_PP_CAT(PseudoPMIntVTable, c) pseudopm_table = \
{ \
BOOST_PP_SEQ_FOR_EACH_I(PSEUDOPMX_INIT_VTABLE_ENTRY, c, memfns) \
}; \
pseudopm_vtable_.Reset(pseudopm_table);
// The base class should call this macro in its definition (at class scope).
// This defines the virtual table structs, enumerations, internal functions,
// and declares the public member functions. 'classes' is the sequence of
// classes and 'memfns' is the member function sequence.
#define PSEUDOPM_DECLARE_VTABLE(classes, memfns) \
protected: \
BOOST_PP_SEQ_FOR_EACH(PSEUDOPMX_DECLARE_VTABLE_STRUCT, memfns, classes) \
\
enum PseudoPMTypeEnum \
{ \
BOOST_PP_SEQ_FOR_EACH_I(PSEUDOPMX_DECLARE_VTABLE_ENUM_MEMBER, x, classes) \
}; \
\
union PseudoPMVTableUnion \
{ \
BOOST_PP_SEQ_FOR_EACH(PSEUDOPMX_DECLARE_VTABLE_UNION_MEMBER, x, classes) \
}; \
\
class PseudoPMVTable \
{ \
public: \
BOOST_PP_SEQ_FOR_EACH(PSEUDOPMX_DECLARE_VTABLE_RESET_FN, x, classes) \
private: \
friend class BOOST_PP_SEQ_HEAD(classes); \
PseudoPMTypeEnum type_; \
PseudoPMVTableUnion table_; \
}; \
\
PseudoPMVTable pseudopm_vtable_; \
\
public: \
BOOST_PP_SEQ_FOR_EACH(PSEUDOPMX_DECLARE_VTABLE_PUBLIC_FN, x, memfns)
// This macro must be called in some source file after all of the classes in
// the classes sequence have been defined (so, for example, you can create a
// .cpp file, include all the class headers, and then call this macro. It
// actually defines the public member functions for the base class. Each of
// the public member functions calls the correct member function in the
// derived class. 'classes' is the sequence of classes and 'memfns' is the
// member function sequence.
#define PSEUDOPM_DEFINE_VTABLE(classes, memfns) \
BOOST_PP_SEQ_FOR_EACH(PSEUDOPMX_DEFINE_VTABLE_FN, classes, memfns)
(We should make the vtable static, but I'll leave that as an excercise for the reader. :-D)
Now that that is out of the way, we can actually look at what you need to do in your application to use this.
First, we need to define the list of classes that are going to be in our class hierarchy:
// The sequence of classes in the class hierarchy. The base class must be the
// first class in the sequence. Derived classes can be in any order.
#define CLASSES (Base)(Derived)
Second, we need to define the list of "virtual" member functions. Note that with this (admittedly limited) implementation, the base class and every derived class must implement every one of the "virtual" member functions. If a class doesn't define one of these, the compiler will get angry.
// The sequence of "virtual" member functions. Each entry in the sequence is a
// four-element tuple:
// (1) The name of the function. A function will be declared in the Base class
// with this name; it will do the dispatch. All of the classes in the class
// sequence must implement a private implementation function with the same
// name, but with "Impl" appended to it (so, if you declare a function here
// named "Foo" then each class must define a "FooImpl" function.
// (2) The return type of the function.
// (3) The number of arguments the function takes (arity).
// (4) The arguments tuple. Its arity must match the number specified in (3).
#define VIRTUAL_FUNCTIONS \
((FuncNoArg, void, 0, ())) \
((FuncOneArg, int, 1, (int))) \
((FuncTwoArg, int, 2, (int, int)))
Note that you can name these two macros whatever you want; you'll just have to update the references in the following snippets.
Next, we can define our classes. In the base class, we need to call PSEUDOPM_DECLARE_VTABLE
to declare the virtual member functions and define all the boilerplate for us. In all of our class constructors, we need to call PSEUDOPM_INIT_VTABLE
; this macro generates the code required to initialize the vtable correctly.
In each class we must also define all of the member functions we listed above in the VIRTUAL_FUNCTIONS
sequence. Note that we need to name the implementations with an Impl
suffix; this is because the implementations are always called through the dispatcher functions that are generated by the PSEUDOPM_DECLARE_VTABLE
macro.
class Base
{
public:
Base()
{
PSEUDOPM_INIT_VTABLE(Base, VIRTUAL_FUNCTIONS)
}
PSEUDOPM_DECLARE_VTABLE(CLASSES, VIRTUAL_FUNCTIONS)
private:
void FuncNoArgImpl() { }
int FuncOneArgImpl(int x) { return x; }
int FuncTwoArgImpl(int x, int y) { return x + y; }
};
class Derived : public Base
{
public:
Derived()
{
PSEUDOPM_INIT_VTABLE(Derived, VIRTUAL_FUNCTIONS)
}
private:
void FuncNoArgImpl() { }
int FuncOneArgImpl(int x) { return 2 * x; }
int FuncTwoArgImpl(int x, int y) { return 2 * (x + y); }
};
Finally, in some source file, you'll need to include all the headers where all the classes are defined and call the PSEUDOPM_DEFINE_VTABLE
macro; this macro actually defines the dispatcher functions. This macro cannot be used if all of the classes have not yet been defined (it has to static_cast
the base class this
pointer, and this will fail if the compiler doesn't know that the derived class is actually derived from the base class).
PSEUDOPM_DEFINE_VTABLE(CLASSES, VIRTUAL_FUNCTIONS)
Here is some test code that demonstrates the functionality:
#include <cassert>
int main()
{
Base* obj0 = new Base;
Base* obj1 = new Derived;
obj0->FuncNoArg(); // calls Base::FuncNoArg
obj1->FuncNoArg(); // calls Derived::FuncNoArg
assert(obj0->FuncTwoArg(2, 10) == 12); // Calls Base::FuncTwoArg
assert(obj1->FuncTwoArg(2, 10) == 24); // Calls Derived::FuncTwoArg
}
[Disclaimer: This code is only partially tested. It may contain bugs. (In fact, it probably does; I wrote most of it at 1 am this morning :-P)]