虚拟方法或功能指针

dor*_*ron 32 c++ virtual-functions function-pointers

在C++中实现多态行为时,可以使用纯虚方法,也可以使用函数指针(或函子).例如,异步回调可以通过以下方式实现:

方法1

class Callback
{
public:
    Callback();
    ~Callback();
    void go();
protected:
    virtual void doGo() = 0;  
};

//Constructor and Destructor

void Callback::go()
{
   doGo();
}
Run Code Online (Sandbox Code Playgroud)

因此,要在此处使用回调,您需要覆盖doGo()方法以调用您想要的任何函数

方法2

typedef void (CallbackFunction*)(void*)

class Callback
{
public:
    Callback(CallbackFunction* func, void* param);
    ~Callback();
    void go();
private:
   CallbackFunction* iFunc;
   void* iParam;
};

Callback::Callback(CallbackFunction* func, void* param) :
    iFunc(func),
    iParam(param)
{}

//Destructor

void go()
{
    (*iFunc)(iParam);
}
Run Code Online (Sandbox Code Playgroud)

要在此处使用回调方法,您需要创建一个由Callback对象调用的函数指针.

方法3

[这是我(Andreas)在问题中添加的; 它不是由原始海报写的]

template <typename T>
class Callback
{
public:
    Callback() {}
    ~Callback() {}
    void go() {
        T t; t();
    }
};

class CallbackTest
{
public:
    void operator()() { cout << "Test"; }
};

int main()
{
    Callback<CallbackTest> test;

    test.go();
}
Run Code Online (Sandbox Code Playgroud)

每种实施的优缺点是什么?

Adi*_*sak 13

方法1(虚函数)

  • "+""用C++完成它的正确方法
  • " - "每个回调必须创建一个新类
  • " - "性能方面,通过VF-Table与功能指针进行额外的解引用.与Functor解决方案相比,两个间接引用.

方法2(带功能指针的类)

  • "+"可以为C++ Callback Class包装C风格的函数
  • "+"回调对象创建后可以更改回调函数
  • " - "需要间接呼叫.对于可以在编译时静态计算的回调,可能比functor方法慢.

方法3(类调用T函子)

  • "+"可能是最快的方式.没有间接的呼叫开销,可能完全内联.
  • " - "需要定义一个额外的Functor类.
  • " - "要求在编译时静态声明回调.

FWIW,功能指针与Functors不同.Functors(在C++中)是用于提供函数调用的类,通常是operator().

这是一个示例仿函数以及一个使用仿函数参数的模板函数:

class TFunctor
{
public:
    void operator()(const char *charstring)
    {
        printf(charstring);
    }
};

template<class T> void CallFunctor(T& functor_arg,const char *charstring)
{
    functor_arg(charstring);
};

int main()
{
    TFunctor foo;
    CallFunctor(foo,"hello world\n");
}
Run Code Online (Sandbox Code Playgroud)

从性能角度来看,虚函数和函数指针都会导致间接函数调用(即通过寄存器),尽管虚函数在加载函数指针之前需要额外加载VFTABLE指针.使用Functors(使用非虚拟调用)作为回调是使用参数来模板函数的最高性能方法,因为它们可以内联,即使没有内联,也不会生成间接调用.


Tho*_*ini 6

方法1

  • 更易于阅读和理解
  • 错误的可能性较小(iFunc不能为NULL,你没有使用void *iParam等等
  • C++程序员会告诉你,这是用C++实现它的"正确"方法

方法2

  • 打字要少一些
  • 非常快(调用虚方法有一些开销,通常是两个简单的算术运算相同..所以它很可能无关紧要)
  • 这就是你在C中的表现

方法3

可能是在可能的情况下做到这一点的最好方法.它将具有最佳性能,它将是类型安全的,并且它易于理解(这是STL使用的方法).

  • 当虚拟函数阻止内联小函数,删除重复或不可能的分支和/或使用仅寄存器变量存储时,虚函数可能会慢40倍. (4认同)
  • 方法3不是最好的方法.因为您需要打破类的封装以允许任意对象访问数据. (2认同)

Pup*_*ppy 5

方法2的主要问题是它根本无法扩展.考虑100个函数的等价物:

class MahClass {
    // 100 pointers of various types
public:
    MahClass() { // set all 100 pointers }
    MahClass(const MahClass& other) {
        // copy all 100 function pointers
    }
};
Run Code Online (Sandbox Code Playgroud)

MahClass的规模已经膨胀,构建它的时间也显着增加.但是,虚函数的类大小构造它的时间增加了O(1)- 更不用说你,用户必须手动为所有派生类编写所有回调函数,调整指针成为一个指向派生的指针,必须指定函数指针类型和什么乱七八糟的东西.更不用说你可能会忘记一个,或者将它设置为NULL或者同样愚蠢但完全发生的事情,因为你用这种方式编写了30个类,并且像寄生黄蜂一样违反了干扰毛虫.

方法3仅在所需回调是静态可知时才可用.

这使得方法1成为需要动态方法调用时唯一可用的方法.