繁体   English   中英

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

[英]Can I get polymorphic behavior without using virtual functions?

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

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来调用DeriveddoSomething

我知道我可以只是把virtual之前doSomething()Base就解决这个问题,但我通过我的设备的限制,编译器不支持它。

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

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

由于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()
}

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

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

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

虽然这并不能保证'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)

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

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

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

这是一个实现Base类的程序,该函数返回一个string :“base”,以及一个返回stringDerived类:“der”。 我们的想法是能够支持这样的代码:

Base* obj = new Der;
cout << obj->get_string();

...即使我们通过Base指针调用并使用不支持virtual的编译器, get_string()调用也将返回“der”。

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

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

#include <cstdlib>
#include <string>
#include <iostream>
using namespace std;

class Base
{
public:
    typedef string (Base::*vptr_get_string)(void) const;
    Base(vptr_get_string=0);
    void set_derived_pointer(Base* derived);

    string get_string() const;

protected:
    Base* der_ptr_;
    vptr_get_string get_string_vf_;
};

Base::Base(vptr_get_string get_string_vf)
:   der_ptr_(0),
    get_string_vf_(get_string_vf)
{
}

void Base::set_derived_pointer(Base* derived)
{
    der_ptr_ = derived;
}

string Base::get_string() const
{
    if( get_string_vf_ )
        return (der_ptr_->*get_string_vf_)();
    else
        return "base";
}

class Der : public Base
{
public:
    Der();
    string get_string() const;
};

Der::Der()
:   Base(static_cast<Base::vptr_get_string>(&Der::get_string))
{
    set_derived_pointer(this);
}

string Der::get_string() const
{
    return "der";
}

int main()
{
    Base* obj = new Der;
    cout << obj->get_string();
    delete obj;
}

我想你可以制作自己的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;}
};

你可以封装基类而不是从它派生吗?

然后你可以调用doSomething()//派生
或base-> doSomething()//调用base

您可以使用模板进行编译时多态。

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;

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

为理解小程序,我们可以使用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;
}

输出:

来自基类来自派生类

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

我认为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();
}

暂无
暂无

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM