抽象指向成员函数的指针:安全性和替代方案

Posted

技术标签:

【中文标题】抽象指向成员函数的指针:安全性和替代方案【英文标题】:Abstracting pointer-to-member-function: safety and alternatives 【发布时间】:2014-09-12 19:19:41 【问题描述】:

在这个问题中,假设我们已经以一种很好、谨慎的方式处理了所有指针——为了防止问题膨胀,我不想在这里包含我的清理代码!

假设我们有两个类,FooBar,其类定义如下:

class Foo

    public:
        Foo();
        void fooFn();
;

class Bar

    public:
        Bar();
        void barFn();
;

假设FooBar必须没有继承关系,但是我们需要同时调用fooFnbarFn来响应一些刺激。我们可以创建一个带有容器的控制器类,从该容器中调用 FooBar 的特定实例上的 fooFnbarFn - 例如静态 std::vector - 但我们遇到了一个问题:指向成员的指针FooBar 实例的函数类型不同。

通过在控制器类中使用静态vector< std::function<void()>* >,我们可以解决问题。 FooBar 实例可以具有一个函数,该函数通过捕获 this 的 lambda 函数添​​加指向向量的指针:

void Foo::registerFnPointer()

    ControllerClass::function_vector.push_back( new [this]() return this->fooFn();  );

我已经测试过这个方法,它似乎没有任何问题。也就是说,我担心绕过前面提到的类型差异可能导致的问题......我什么都不担心吗?有没有更好的方法来完成等效功能?

【问题讨论】:

这些不是forward declarations。 为什么是vector<std::function<void()>*> 而不是简单的vector<std::function<void()>>ControllerClass::function_vector.push_back([this] return fooFn(); ); @Conduit 你称之为类声明。 前向声明将是class Foo;。我猜你可以说你用前向声明成员函数来声明类,但对于函数来说更常见的是声明和定义。 你也可以只使用std::bindsee it live. 【参考方案1】:

我看到的唯一问题实际上与函子无关,而是与对象生命周期有关。那就是:我不确定您如何确保在 Foo 或 Bar 实例被销毁时始终取消注册在 ControllerClass 中注册的仿函数。

但是你提到你做了适当的内存管理。

在我看来,您不需要存储指向function<void()> 的指针,您可以简单地将函数存储为值(即具有vector<function<void()>>)。

在 C++11 和 lambdas 之前,为了达到相同的效果,您也可以使用 (boost) 函数,但您会使用 boost::bind 与 fooFn 的地址和绑定到的第一个参数指向 Foo 对象实例的指针(或引用)。

这将创建一个函数实例,该实例包含调用给定对象上的 fooFn 方法所需的所有信息。然后,您可以将该实例存储在一个向量中以便稍后调用它(并且遇到相同的问题,即确保没有绑定到已销毁对象的 boost::function 保持注册)

编辑:

为了完整起见,特定于绑定成员的 Boost 绑定文档的链接:http://www.boost.org/doc/libs/1_56_0/libs/bind/bind.html#with_member_pointers

您所做的实际上非常相似,只是您现在使用 lambda 来捕获对象指针并定义要调用的函数。

所以我认为你正在做的事情没有问题(除了我已经提到的那个)。

【讨论】:

我没有看到 C++11 之前部分答案的意义。 这个问题被标记为 C++11,但在我看来,这里唯一的 C++11 细节是 std::function 现在是 STL 和 lambda 的一部分。他遇到的问题其实是C++11独立的……我也想说,他现在做的不是以前做不到的。同时要说:旧的做事方式很好(没问题)。新方法(你正在做的事情)实际上几乎相同 -> 并且很好......并且还描述了实现相同目标的另一种方法。 谁知道呢,也许无法切换到支持 C++11 的最新版本编译器的人可能会在某个时间点欣赏这一点... 历史课有助于正确看待其余的答案,imo。谢谢!【参考方案2】:

您可以使用适配器类。这对你正在做的事情来说可能有点矫枉过正,但它可能会奏效。

这样做的好处是:

    您不必更改原始类。创建void Foo::registerFnPointer() 很丑。

    您不必使用静态std::vector

    您不必处理函数指针。

假设你有两个不同的类:

struct Foo

    void fooFn ()  
        std::cout << "Foo::fooFn ()" "\n" ; 
    
;

struct Bar

    void barFn () 
        std::cout << "Bar::barFn ()" "\n" ; 
    
;

目标是将它们放入容器中并调用它们各自的*Fn ()成员函数。

适配器看起来像这样:

struct Adapter_Base

    virtual ~Adapter_Base ()  ;

    virtual void adapterFn () = 0 ;
;

template <typename T>
struct Adapter : Adapter_Base

    T tVal ;

    Adapter (const T &tVal) : tVal (tVal) 
    void adapterFn () ;
;

template <>
void Adapter <Foo>::adapterFn ()

    tVal.fooFn () ;


template <>
void Adapter <Bar>::adapterFn ()

    tVal.barFn () ;

你可以这样使用它:

int main ()

    std::vector <std::unique_ptr <Adapter_Base> > v1 ;

    std::unique_ptr <Adapter_Base> u1 (new Adapter <Foo> (Foo ())) ;
    std::unique_ptr <Adapter_Base> u2 (new Adapter <Bar> (Bar ())) ;

    v1.push_back (std::move (u1)) ;
    v1.push_back (std::move (u2)) ;

    for (auto &adapter : v1) 
        adapter->adapterFn () ;
    

    return 0 ;

【讨论】:

他现在做的还可以。 std::function 实际上正在执行您的适配器所做的事情(它保留要调用的实例和函数)。代码少得多。他实际上是在问他所做的是否可以,是否有更好的方法来完成同样的事情。我不会把你的提议称为更好的解决方案。 @ds27680 在 OP 的帖子中,他正在为每个类添加一个新的成员函数。这样做,您不必更改原始类。您也不必处理使用静态向量或处理函数指针。 但是适配器没有解决新的成员函数“问题”。您只需将他在 registerFnPointer 中编写的代码移动到 main 中。他选择以一种新的课程方法进行注册(即使有争议)与他的问题无关。他本可以编写与您在 main.xml 中编写的相同(我的意思是相同)代码。解决静态向量问题也是如此。您将矢量移动到 main... 这只是对 std::function 的可怕重新实现。

以上是关于抽象指向成员函数的指针:安全性和替代方案的主要内容,如果未能解决你的问题,请参考以下文章

如何从静态成员函数调用指向成员函数的指针?

C++|详解类成员指针:数据成员指针和成员函数指针及应用场合

c++怎样将指向【成员函数】的指针强转成指向【普通函数】的指针

C++ 成员函数指针和 STL 算法

C++函数指针与成员函数指针

成员函数指针和指向静态成员函数的指针