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

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,来调用doSomethingDerived

我知道我可以只是把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,您应该获得派生的实现.

  • @dreamlax,如果虚函数不起作用,那么dynamic_cast也不太可能. (13认同)
  • 这假设程序员在铸造时仍然知道确切的派生类型,这是一个完全无关紧要的案例,并且恕我直言并没有回答提问者的意图(尽管他通过粗心地分组步骤来邀请这种误读).哎呀,要使用它,你需要保证`Base*obj`赋值和转换的顺序,在这种情况下你只需要使用`Derived*obj`开头.真正的解决方案:将其与类型描述性数据和铸件之间的切换相结合; 函数指针; 如果可以在编译时解析行为,则使用OO上的模板. (7认同)
  • 你不应该使用`dynamic_cast`来建立一个派生的基础吗?(我是C++菜鸟) (5认同)
  • 对; 为了使用`dynamic_cast`,该类必须是多态的(这意味着它或它的一个基类必须至少有一个虚函数). (5认同)
  • @dreamlax:首先,你*可以*使用`dynamic_cast`来强制派生,但这并不意味着你*应该*一直这样做.其次,`dynamic_cast`仅适用于多态类,在这种情况下,我们特别具有非多态类.在这种情况下不能使用`dynamic_cast`. (4认同)

Jam*_*lis 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()
}
Run Code Online (Sandbox Code Playgroud)

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

  • 好的插图詹姆斯. (2认同)

wkl*_*wkl 6

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

static_cast<Derived*>(obj)->doSomething();
Run Code Online (Sandbox Code Playgroud)

虽然这并不能保证'obj'指向真正的类型Derived.

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

  • 使用析构函数,您必须非常小心,不要通过基类指针"删除"派生类对象. (5认同)
  • James McNellis关于删除派生类是正确的,感谢您提醒我。我想知道这是否是他正在使用的编译器的实际限制,或者原始发布者是否遵循某些准则,或者由于动态分配的开销而由于性能原因而被限制使用虚拟函数? (2认同)

Jam*_*lis 6

我的第一个答案表明,确实有可能至少得到一种有限形式的多态性行为,而实际上并不依赖于语言对多态性的支持.

但是,该示例具有大量的样板.它肯定不会很好地扩展:对于你添加的每个类,你必须修改代码中的六个不同的位置,并且对于你想要支持的每个成员函数,你需要复制大部分代码.呸.

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