Мой первый ответ показывает, что действительно возможно получить хотя бы ограниченную форму полиморфного поведения, фактически не полагаясь на поддержку языка полиморфизма.
Однако в этом примере содержится огромное количество шаблонов.Это, конечно, не будет хорошо масштабироваться: для каждого добавляемого вами класса вам нужно изменить шесть разных мест в коде, а для каждой функции-члена, которую вы хотите поддерживать, вам нужно дублировать большую часть этого кода.Тьфу.
Что ж, хорошие новости: с помощью препроцессора (и, конечно, библиотеки Boost.Preprocessor) мы можем легко извлечь большую часть этой таблицы и сделать это решение управляемым.
Чтобы убрать шаблон с пути, вам понадобятся эти макросы.Вы можете поместить их в заголовочный файл и забыть о них, если хотите;они довольно общие.[Пожалуйста, не убегайте после прочтения этого;если вы не знакомы с библиотекой Boost.Preprocessor, она, вероятно, выглядит ужасно :-) После этого первого блока кода мы увидим, как мы можем использовать это, чтобы сделать код нашего приложения намного чище.Если вы хотите, вы можете просто проигнорировать детали этого кода.]
Код представлен в том порядке, в каком он есть, потому что если вы копируете и вставляете каждый из блоков кода из этого поста, по порядку, вИсходный файл C ++, он (я имею в виду, должен!) Компилируется и запускается.
Я назвал это «Псевдополиморфной библиотекой»;любые имена, начинающиеся с «PseudoPM» с любой заглавной буквы, должны рассматриваться как зарезервированные им.Макросы, начинающиеся с PSEUDOPM
, являются публично вызываемыми макросами;макросы, начинающиеся с PSEUDOPMX
, предназначены для внутреннего использования.
#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)
(Мы должны сделать vtable статическим, но я оставлю это в качестве упражнения для читателя. :-D)
Теперь, когда это не так, мы можем посмотреть, что вам нужно сделать в вашем приложении, чтобы использовать это.
Сначала нам нужно определить список классов, которые будут в нашем приложении.иерархия классов:
// 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)
Во-вторых, нам нужно определить список «виртуальных» функций-членов.Обратите внимание, что в этой (предположительно ограниченной) реализации базовый класс и каждый производный класс должны реализовывать каждую из «виртуальных» функций-членов.Если класс не определит один из них, компилятор рассердится.
// 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)))
Обратите внимание, что вы можете назвать эти два макроса как угодно;вам просто нужно обновить ссылки в следующих фрагментах.
Далее мы можем определить наши классы.В базовом классе нам нужно вызвать PSEUDOPM_DECLARE_VTABLE
, чтобы объявить виртуальные функции-члены и определить весь шаблон для нас.Во всех наших конструкторах классов нам нужно вызывать PSEUDOPM_INIT_VTABLE
;этот макрос генерирует код, необходимый для правильной инициализации vtable.
В каждом классе мы также должны определить все функции-члены, перечисленные выше в последовательности VIRTUAL_FUNCTIONS
.Обратите внимание, что мы должны назвать реализации с суффиксом Impl
;это потому, что реализации всегда вызываются через функции диспетчера, которые генерируются макросом PSEUDOPM_DECLARE_VTABLE
.
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); }
};
Наконец, в некоторый исходный файл вам нужно будет включить все заголовки, где всеклассы определены и вызывают макрос PSEUDOPM_DEFINE_VTABLE
;этот макрос фактически определяет функции диспетчера.Этот макрос не может использоваться, если все классы еще не определены (он должен static_cast
указатель this
базового класса *, и это не удастся, если компилятор не знает, что производный класс на самом деле является производным отбазовый класс).
PSEUDOPM_DEFINE_VTABLE(CLASSES, VIRTUAL_FUNCTIONS)
Вот некоторый тестовый код, демонстрирующий функциональность:
#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
}
[Отказ от ответственности: Этот код протестирован только частично.Может содержать ошибки.(На самом деле, вероятно, так и есть; большую часть времени я написал в 1 час утра :-P)]