为什么在C++中要使用成员函数指针?

4
许多C++书籍和教程都解释了如何做到这一点,但我没有看到一个给出令人信服的理由来选择这样做。
我非常清楚为什么在C中需要函数指针(例如,在使用某些POSIX设施时)。然而,据我所知,您无法发送它们成员函数,因为有“this”参数。但是,如果您已经在使用类和对象,则为什么不使用面向对象的解决方案,如函数对象?
欢迎提供真实世界中必须使用此类函数指针的示例。
更新:感谢大家的答案。不过,我必须说,这些示例都没有让我相信从纯OO角度来看这是一种有效的机制...

1
“必须使用”是指“不能没有这个”吗? - Mykola Golubyev
我是说“有一个充分正当的理由使用它”。 - Uri
我经常想到和你的问题一样的事情。现在我不必再问了... - Dan
10个回答

13

在C++中,“函数对象(Functors)”通常指定义了一个带有任意参数和返回值的operator()的结构体,可以用作真实函数或函数指针的语法替代品。但是,它们的面向对象问题存在许多问题,首要问题是可用性。这需要大量复杂的样板代码。为了像大多数对话框架那样实现良好的信号框架,需要大量继承的混乱。

在这里,实例绑定的函数指针会非常有益(.NET用委托充分展示了这一点)。

然而,C++成员函数指针仍然满足另一个需求。例如,假设您有很多值在列表中,您想执行其中一个方法,比如其print()方法。此时,指向YourType::size的函数指针就很有用,因为它可以让您编写这样的代码:

std::for_each(lst.begin(), lst.end(), std::mem_fun(&YourType::print))

我以前从来不知道可以像这样使用成员函数指针..而且一直想知道没有this指针的情况下它们有什么用处。现在我可以避免编写不必要的函数对象了。 - Naveen
注意:mem_fun的使用可以被更清晰的boost :: bind(&YourType :: print,_1)所替代 - 也就是说,假设您不想出于任何原因使用真正的for循环。 - Iraimbilanja
当然,你的例子具有讽刺意味的是,std::mem_fun返回一个包装成员函数的函数对象 :-P。所以即使如此,你仍然给出了一个函数对象的例子... - Evan Teran
那是一个好答案,虽然我不得不承认我仍然不喜欢这个机制,它对我来说太像"switch"了。 - Uri

3
过去,成员函数指针在以下场景中很有用:
class Image {
    // avoid duplicating the loop code
    void each(void(Image::* callback)(Point)) {
        for(int x = 0; x < w; x++)
            for(int y = 0; y < h; y++)
                callback(Point(x, y));
    }

    void applyGreyscale() { each(&Image::greyscalePixel); }
    void greyscalePixel(Point p) {
        Color c = pixels[p];
        pixels[p] = Color::fromHsv(0, 0, (c.r() + c.g() + c.b()) / 3);
    }

    void applyInvert() { each(&Image::invertPixel); }
    void invertPixel(Point p) {
        Color c = pixels[p];
        pixels[p] = Color::fromRgb(255 - c.r(), 255 - r.g(), 255 - r.b());
    }
};

我曾在商业绘画应用程序中看到过它的使用。(有趣的是,这是为数不多的最好用预处理器解决的C++问题之一)。

然而,如今成员函数指针的唯一用途是在boost::bind的实现中使用。


我不会说boost::bind是唯一的用途。成员函数指针经常用于实现用户定义的转换为布尔类型。它们作为布尔类型的优点在于它们不会自动提升为整数。 - Nathan Kitchen

2

这里有一个我们常见的典型场景。我们有一个通知框架,在这个框架中,一个类可以注册到多个不同的通知上。在注册通知时,我们会传递成员函数指针。这与C#的事件非常相似。

class MyClass
{
    MyClass()
    {
        NotificationMgr::Register( FunctionPtr( this, OnNotification ) );
    }
    ~MyClass()
    {
        NotificationMgr::UnRegister( FunctionPtr( this, OnNotification ) );
    }

    void OnNotification( ... )
    {
        // handle notification
    }
};

在Java中,标准惯用法是为可通知项创建一个接口。在C++中,使用抽象类作为更清晰的设计是否也可以呢? - Uri
当你拥有一个接口时,如果类注册了许多通知,它可能变得复杂难以管理。回调的名称由接口强制执行。如果许多接口具有相同的名称,你如何管理它们?在C#中,在上述示例中,类决定回调名称。 - decasteljau

2

我目前正在处理一些代码,其中我使用它们来实现状态机。解除引用的成员函数实现各个状态,但由于它们都在类中,所以它们可以共享整个状态机的全局数据。如果使用普通(非成员)函数指针,则这将很难实现。

然而,我仍然未决定是否这是实现状态机的好方法。


0
成员指针最重要的用途之一是创建函数对象。好消息是,你几乎不需要直接使用它,因为它已经在像boost::bind这样的库中解决了,但你确实需要将指针传递给这些库。
class Processor
{
public:
   void operation( int value );
   void another_operation( int value );
};
int main()
{
   Processor tc;
   boost::thread thr1( boost::bind( &Processor::operation, &tc, 100 ) );
   boost::thread thr2( boost::bind( &Processor::another_operation, &tc, 5 ) );
   thr1.join();
   thr2.join();
}

您可以看到创建一个线程并在给定类的实例上执行给定操作的简单性。

上述问题的简单手工方法是创建自己的函数对象:

class functor1
{
public:
    functor1( Processor& o, int v ) : o_(o), v_(v) {}
    void operator()() {
        o_.operation( v_ ); // [1]
    }
private:
    Processor& o_;
    int v_;
};

对于您想要调用的每个成员函数,创建一个不同的函数对象。请注意,operationanother_operation使用的函数对象是完全相同的,在[1]中的调用必须在两个函数对象中都复制一遍。通过使用成员函数指针,您可以编写一个简单的函数对象:

class functor
{
public:
   functor( void (*Processor::member)(int), Processor& p, int value )
      : member_( member ), processor_(p), value_( value ) {}

   void operator()() {
      p.*member(value_);
   }
private:
   void (*Processor::member_)(int);
   Processor& processor_;
   int value;
};

并使用它:

int main() {
   Processor p;
   boost::thread thr1( functor( &Processor::operation, p, 100 ) );
   boost::thread thr2( functor( &Processor::another_operation, p, 5 ) );
   thr1.join();
   thr2.join();
}

然而,您甚至不需要定义该函数对象,因为boost::bind已经为您完成了。即将发布的标准将会有自己的版本的bind,沿用boost的实现方式。


0

成员函数指针是与对象无关的。如果您想在运行时(或作为模板参数)按值引用函数,则需要它。当您没有特定的对象可调用它时,它就会发挥作用。

因此,如果您知道函数,但不知道对象,并且希望通过值传递此知识,则成员函数指针是唯一的规定解决方案。Iraimbilanja的示例很好地说明了这一点。它可能有助于您查看我的成员变量示例用法。原理是相同的。


0

这就像使用lambda表达式一样。你总是可以将所有必要的本地变量传递给一个简单的函数,但有时你需要传递多个变量。

因此,使用成员函数可以避免将所有必要的成员字段传递给一个函数对象。就是这样。


0

你特别询问了成员函数,但是函数指针还有其他用途。在C++中,我需要使用函数指针的最常见原因是想要在运行时使用LoadLibrary()加载DLL。这显然是在Windows中。在使用可选DLL形式的插件的应用程序中,由于DLL通常不存在,并且使用延迟加载很麻烦,因此无法在应用程序启动时使用动态链接。

加载库之后,您必须获取要使用的函数的指针。


0

我已经使用成员函数指针来解析文件。根据在文件中找到的特定字符串,在映射中找到相同的值并调用相关函数。这代替了一个大型的if..else if..else语句来比较字符串。


-1

在一个场景中,我需要向第三方API对象提供一个回调函数指针,并且该回调函数具有预定义的参数列表(因此无法传递任意参数),我使用了成员函数的函数指针。

由于回调函数要处理基于对象状态的传入事件,而这个对象又使用了第三方API触发了回调,所以我无法在全局命名空间中实现回调函数。

因此,我希望回调函数的实现是在使用第三方对象的类的一部分。我在我想要实现回调的类中声明了一个公共静态成员函数,并将其指针传递给API对象(static关键字避免了this指针的麻烦)。

我的对象的this指针随后作为回调的Refcon的一部分传递(幸运的是,它包含了通用的void*)。 然后,虚拟函数的实现使用传递的指针来调用包含在类中的实际的私有实现的回调。

代码大概是这样的:

public:
    void SomeClass::DummyCallback( void* pRefCon ) [ static ]
    {
        reinterpret_cast<SomeClassT*>(pRefCon)->Callback();
    }
private:
    void class SomeClass::Callback() [ static ]
    {
        // some code ...
    }

这不是成员函数指针的示例,而是将this指针传递给C风格回调并使用对象调用普通成员函数的示例。成员函数指针与此无关。 - Evan Teran
虽然我没有能力在C++中使用函数指针来调用静态成员函数,但是我描述的内容也无法实现,如果我没错的话?抱歉,如果这与主题无关,但我认为这是函数指针指向成员的有效用法... - Bjoern
静态成员在函数指针方面与C++成员函数完全不同。静态成员不需要“this”指针(因此您可以将它们用于该目的)。虽然静态成员不需要C样式链接... - Evan Teran

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接