簡體   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