oka*_*ami 18 c++ polymorphism virtual overriding function
由于我的设备,我无法使用虚拟功能.假设我有:
class Base
{
void doSomething() { }
};
class Derived : public Base
{
void doSomething() { }
};
// in any place
{
Base *obj = new Derived;
obj->doSomething();
}
Run Code Online (Sandbox Code Playgroud)
在obj->doSomething()将调用只是Base::doSomething()
有没有一种方法Base *obj,来调用doSomething的Derived?
我知道我可以只是把virtual之前doSomething()的Base它解决的问题,但我通过我的设备的限制,编译器不支持它.
pax*_*977 15
您可以将基类指针向下转换为派生类并调用该函数.
Base* obj = new Derived;
Derived* d = static_cast<Derived*>( obj );
d->doSomething();
Run Code Online (Sandbox Code Playgroud)
由于doSomething()未声明virtual,您应该获得派生的实现.
当然可以这样做; 它不一定容易.
如果存在有限的派生类列表,并且在定义基类时知道它们是什么,则可以使用非多态成员函数包装器来完成此操作.以下是两个派生类的示例.它不使用标准库设施,仅依赖于标准C++功能.
class Base;
class Derived1;
class Derived2;
class MemFnWrapper
{
public:
enum DerivedType { BaseType, Derived1Type, Derived2Type };
typedef void(Base::*BaseFnType)();
typedef void(Derived1::*Derived1FnType)();
typedef void(Derived2::*Derived2FnType)();
MemFnWrapper(BaseFnType fn) : type_(BaseType) { fn_.baseFn_ = fn; }
MemFnWrapper(Derived1FnType fn) : type_(Derived1Type) {fn_.derived1Fn_ = fn;}
MemFnWrapper(Derived2FnType fn) : type_(Derived2Type) {fn_.derived2Fn_ = fn;}
void operator()(Base* ptr) const;
private:
union FnUnion
{
BaseFnType baseFn_;
Derived1FnType derived1Fn_;
Derived2FnType derived2Fn_;
};
DerivedType type_;
FnUnion fn_;
};
class Base
{
public:
Base() : doSomethingImpl(&Base::myDoSomething) { }
Base(MemFnWrapper::Derived1FnType f) : doSomethingImpl(f) { }
Base(MemFnWrapper::Derived2FnType f) : doSomethingImpl(f) { }
void doSomething() { doSomethingImpl(this); }
private:
void myDoSomething() { }
MemFnWrapper doSomethingImpl;
};
class Derived1 : public Base
{
public:
Derived1() : Base(&Derived1::myDoSomething) { }
private:
void myDoSomething() { }
};
class Derived2 : public Base
{
public:
Derived2() : Base(&Derived2::myDoSomething) { }
private:
void myDoSomething() { }
};
// Complete the MemFnWrapper function call operator; this has to be after the
// definitions of Derived1 and Derived2 so the cast is valid:
void MemFnWrapper::operator()(Base* ptr) const
{
switch (type_)
{
case BaseType: return (ptr->*(fn_.baseFn_))();
case Derived1Type: return (static_cast<Derived1*>(ptr)->*(fn_.derived1Fn_))();
case Derived2Type: return (static_cast<Derived2*>(ptr)->*(fn_.derived2Fn_))();
}
}
int main()
{
Base* obj0 = new Base;
Base* obj1 = new Derived1;
Base* obj2 = new Derived2;
obj0->doSomething(); // calls Base::myDoSomething()
obj1->doSomething(); // calls Derived1::myDoSomething()
obj2->doSomething(); // calls Derived2::myDoSomething()
}
Run Code Online (Sandbox Code Playgroud)
(我最初建议使用std::function,它为你做了很多这项工作,但后来我记得它是一个多态函数包装器,所以它必然使用虚函数.: - P糟糕.你可以查看修订历史记录看看那是什么看起来像)
您可以将对象向下转换为Derived类型并调用它,如下所示:
static_cast<Derived*>(obj)->doSomething();
Run Code Online (Sandbox Code Playgroud)
虽然这并不能保证'obj'指向真正的类型Derived.
我更担心你甚至无法访问虚拟功能.如果你的所有函数都不是虚函数,并且你是子类,那么析构函数如何工作?
我的第一个答案表明,确实有可能至少得到一种有限形式的多态性行为,而实际上并不依赖于语言对多态性的支持.
但是,该示例具有大量的样板.它肯定不会很好地扩展:对于你添加的每个类,你必须修改代码中的六个不同的位置,并且对于你想要支持的每个成员函数,你需要复制大部分代码.呸.
嗯,好消息:在预处理器(当然还有Boost.Preprocessor库)的帮助下,我们可以轻松地提取大部分的boilderplate并使这个解决方案易于管理.
为了避免使用样板,您需要这些宏.您可以将它们放在头文件中,如果需要,可以忘记它们; 它们相当通用.[读完之后请不要逃跑; 如果您不熟悉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)
Run Code Online (Sandbox Code Playgroud)
(我们应该让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)
Run Code Online (Sandbox Code Playgroud)
其次,我们需要定义"虚拟"成员函数列表.请注意,使用此(实际上是有限的)实现,基类和每个派生类必须实现每个"虚拟"成员函数.如果一个类没有定义其中一个,编译器就会生气.
// 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)))
Run Code Online (Sandbox Code Playgroud)
请注意,您可以根据需要为这两个宏命名; 您只需更新以下代码段中的引用即可.
接下来,我们可以定义我们的类.在基类中,我们需要调用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); }
};
Run Code Online (Sandbox Code Playgroud)
最后,在某些源文件中,您需要包含定义所有类的所有标头并调用PSEUDOPM_DEFINE_VTABLE宏; 这个宏实际上定义了调度程序的功能.如果尚未定义所有类,则不能使用此宏(它必须static_cast使用基类this指针,如果编译器不知道派生类实际上是从基类派生的,那么这将失败).
PSEUDOPM_DEFINE_VTABLE(CLASSES, VIRTUAL_FUNCTIONS)
Run Code Online (Sandbox Code Playgroud)
以下是一些演示功能的测试代码:
#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
}
Run Code Online (Sandbox Code Playgroud)
[免责声明:此代码仅部分测试.它可能包含错误.(事实上,它可能确实如此;我今天凌晨1点写了大部分内容:-P)]
| 归档时间: |
|
| 查看次数: |
6944 次 |
| 最近记录: |