我可以在不使用虚函数的情况下获得多态行为吗?

时间:2010-09-29 02:05:12

标签: c++ function polymorphism virtual override

由于我的设备,我无法使用虚拟功能。假设我有:

class Base
{
    void doSomething() { }
};

class Derived : public Base
{
    void doSomething() { }
};

// in any place
{
    Base *obj = new Derived;
    obj->doSomething();
}

obj->doSomething()只会调用Base::doSomething()

是否可以使用Base *obj来调用doSomething的{​​{1}}?

我知道我可以在Derived virtual之前放置一个doSomething()来解决问题,但是我受到设备的限制,编译器不支持它。< / p>

11 个答案:

答案 0 :(得分:14)

您可以将基类指针向下转换为派生类并调用该函数。

Base* obj = new Derived;
Derived* d = static_cast<Derived*>( obj ); 
d->doSomething();

由于doSomething()未声明为virtual,因此您应该获得派生的实现。

答案 1 :(得分:9)

当然可以这样做;这不一定容易。

如果存在有限的派生类列表,并且在定义基类时知道它们是什么,则可以使用非多态成员函数包装器来执行此操作。以下是两个派生类的示例。它不使用标准库设施,仅依赖于标准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()
}

(我最初建议使用std::function,它为你做了很多这方面的工作,但后来我记得它是一个多态函数包装器,因此它必然使用虚函数。: - P糟糕。你可以查看修订历史,看看那个看起来像什么)

答案 2 :(得分:6)

您可以将对象向下转换为Derived类型并调用它,如下所示:

static_cast<Derived*>(obj)->doSomething();

虽然这并不能保证“obj”指向的是Derived类型。

我更担心你甚至无法访问虚拟功能。如果你的所有函数都不是虚函数,并且你是子类化的,那么析构函数如何工作?

答案 3 :(得分:6)

My first answer表明,实际上至少可以获得有限形式的多态类行为,而实际上并不依赖于语言对多态的支持。

然而,这个例子有大量的样板。它肯定不会很好地扩展:对于您添加的每个类,您必须修改代码中的六个不同位置,并且对于您要支持的每个成员函数,您需要复制大部分代码。呸。

嗯,好消息:在预处理器(当然还有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)

(我们应该将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)]

答案 4 :(得分:2)

我想你可以制作自己的vtable。我只是一个包含你的“虚拟”函数指针作为Base的一部分的结构,并且有代码来设置vtable。

这是一个重要的解决方案 - 处理此功能是C ++编译器的工作。

但是这里有:

#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;}
};

答案 5 :(得分:2)

由于虚拟方法通常是通过vtable实现的,因此无法在代码中进行复制。实际上,您可以实现自己的虚拟调度机制。它需要一些工作,无论是实现基类的程序员还是实现派生类的程序员,它都可以工作。

如同ceretullis所建议的那样,投射指针可能是你应该考虑做的第一件事。但是我在这里发布的解决方案至少让你有机会编写使用这些类的代码,就好像你的编译器支持virtual一样。也就是说,通过一个简单的函数调用。

这是一个实现Base类的程序,该类具有返回string:“base”的函数,以及返回Derived的{​​{1}}类: DER”。我们的想法是能够支持这样的代码:

string

...以便Base* obj = new Der; cout << obj->get_string(); 调用将返回“der”,即使我们通过get_string()指针调用并使用不支持Base的编译器。

它通过实现我们自己的vtable版本来工作。实际上,它并不是一张桌子。它只是基类中的成员函数指针。在基类的virtual实现中,如果成员函数指针为非null,则调用该函数。如果为null,则执行基类实现。

简单,直接且非常基本。这可能会有很大改善。但它显示了基本技术。

get_string()

答案 6 :(得分:1)

您可以封装基类而不是从中派生吗?

然后你可以调用doSomething()//得到派生的 或base-&gt; doSomething()//调用base

答案 7 :(得分:0)

您可以将模板用于编译时多态。

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;

现在,我们不能指向带有Derived指针的Base,但我们可以使用带有MyClass的模板化函数,这将适用于BaseDerived {{1}}个对象。

答案 8 :(得分:0)

为便于理解的小型程序,我们可以使用static_cast将基类指针转换为派生类并调用该函数。

#include<iostream>

using namespace std;

 class Base
{
  public:

   void display()
    {
      cout<<"From Base class\n";
    }
 };

 class Derived:public Base
 {
   public:

    void display()
    {
      cout<<"From Derived class";

    }
   };

int main()
{
  Base *ptr=new Derived;
  Derived* d = static_cast<Derived*>(ptr);
  ptr->display();
  d->display();
  return 0;
}

输出:

来自基类来自派生类

答案 9 :(得分:-1)

没有虚拟方法,没有简单的方法可以做到这一点。

答案 10 :(得分:-1)

我认为CRTP是可行的(如果你的'设备'支持模板)。

#include <iostream>

template<class T> struct base{
    void g(){
        if(T *p = static_cast<T *>(this)){
            p->f();
        }
    }
    void f(){volatile int v = 0; std::cout << 1;}
    virtual ~base(){}
};

struct derived1 : base<derived1>{
    void f(){std::cout << 2;}
};

struct derived2 : base<derived2>{
    void f(){std::cout << 3;}
};

int main(){
    derived1 d1;
    d1.g();

    derived2 d2;
    d2.g();
}