【问题标题】:Binding a std::function to the same function of a different object instance将 std::function 绑定到不同对象实例的相同函数
【发布时间】:2016-06-08 05:00:39
【问题描述】:

是否可以重新绑定 std::function 以指向相同的函数但具有不同的对象实例?

假设我有一个对象,它的 std::function 绑定到另一个函数,但如果该对象被复制到另一个实例,我想将 std::function 重新绑定到那个新实例,而不是旧实例。

#include "stdafx.h"
#include <iostream>
#include <functional>

class EventHandler
{
public:
    int Num;
    std::function<int()> OnEvent;

    EventHandler (int inNum)
    {
        Num = inNum;
    }

    EventHandler (const EventHandler& other)
    {
        Num = other.Num;
        OnEvent = other.OnEvent; //TODO:  Need some way to redirect the std::function to the new instance rather than having the delegate point to the original object's handler.
    }

    int HandleEvent ()
    {
        return Num;
    }
};

int main()
{
    EventHandler a(4);
    a.OnEvent = std::bind(&EventHandler::HandleEvent, a);
    EventHandler b(a);
    b.Num = 5;
    //Uncommenting the line below is a manual way of redirecting event handler to the new instance.
    //b.OnEvent = std::bind(&EventHandler::HandleEvent, b);

    int aResult = a.OnEvent();
    int bResult = b.OnEvent();

    //This will print out 4 and 4 instead of 4 and 5 since b is still bound to a's event handler.
    std::cout << "aResult=" << aResult << "  bResult=" << bResult << '\n';

    return 0;
}

我愿意使用 std::function 的包装器来存储其他信息。

【问题讨论】:

  • 是如何具体做这件事的问题,还是如何让这个概念在代码中工作?我的意思是,还有其他方法可以正确设置 EventHandler。例如,构造函数可以只是 std::bind(&EventHandler::HandleEvent, this);。还有其他选择,但我不清楚您的目标是什么。
  • 更多的是关于如何让这个概念在代码中发挥作用。为了清楚起见,我想我可能过于简化了这个例子。我正在使用的实际代码库与对象具有父子关系。当一个父级被复制时,它的子级也被复制。我需要迁移孩子的 std::functions 以指向新实例而不是指向原始实例。
  • 让我先说我可能会尝试找到一种不同的方式来组织事情,然后用我要说的来解决这个问题。这听起来像是一个设计问题,可能有一个更简单的解决方案。但是,您可以创建一个模板包装器来存储将在函数中绑定的指向成员的指针。然后,在您的赋值运算符中,您使用指向成员的指针作为目标和“this”作为实例进行绑定。这样做的唯一原因是如果你想让它通用。如果它总是相同的功能,我会重新绑定它。
  • 我的意思是更简单的解决方案是:您可以让子对象具有“设置回调”函数或“初始化父对象”或类似的东西。当您进行分配时,您复制所有子对象并告诉他们将其父对象设置为新实例。或类似的东西。如果您愿意,请告诉我更多详细信息,我们可以进一步讨论。
  • 你不只是想要一个指向成员函数的指针吗?

标签: c++ c++11 std-function


【解决方案1】:

下面的代码引入了一个binding_function&lt;R(Args...)&gt;,它被称为function&lt;R()&gt;,并且参数可以在构造后随时重新绑定(假设它不是nullptr)。

#include <functional>
#include <tuple>
#include <utility>
#include <memory>
#include <iostream>

template <typename T>
class binding_function;

template <typename R, typename... Args>
class binding_function<R(Args...)> : std::function<R()>
{
  using base_function = std::function<R(Args...)>;
  using binded_function = std::function<R()>;
  base_function base;

public:
  binding_function() = default;

  template <typename BaseF, typename... TArgs>
  binding_function(BaseF&& f, TArgs&&... args)
    : base(std::forward<BaseF>(f)) {
    rebind(std::forward<TArgs>(args)...);
  }

  template <typename... TArgs>
  void rebind(TArgs&&... args)
  {
    static_cast<binded_function&>(*this) = 
      std::bind(base, std::forward<TArgs>(args)...);
  }

  using binded_function::operator();
};

class EventHandler
{
public:
    // change type of OnEvent to binding_function
    binding_function<int(EventHandler)> OnEvent;

    // others remain the same
};

int main()
{
    EventHandler a(4);

                // first binding
    a.OnEvent = {&EventHandler::HandleEvent, a};
    EventHandler b(a);
    b.Num = 5;
    b.OnEvent.rebind(b);  // rebinding

    int aResult = a.OnEvent();
    int bResult = b.OnEvent();

    //This will print out 4 and 4 instead of 4 and 5 since b is still bound to a's event handler.
    std::cout << "aResult=" << aResult << "  bResult=" << bResult << '\n';

    return 0;
}

【讨论】:

  • 我还有很多东西要学。我不知道 std::Forward 和 'using' 关键字。我不完全理解第一类定义'binding_function',但出于某种原因需要它。我还在研究这段代码,直到我明白了。这段代码确实很好用!
  • 如果有问题的函数有它自己的参数,这会是什么样子?
【解决方案2】:

您的事件处理程序所做的应该取决于调用它的实例。因此,从逻辑上讲,解决问题的正确方法是将实例作为参数提供给处理函数,例如

#include <iostream>
#include <functional>

class EventHandler
{
private:
  std::function<int(EventHandler &)> handlingFunction;

public:
  int Num;

  EventHandler (int inNum)
  : handlingFunction ([] (EventHandler &) -> int { throw 0; })
  , Num (inNum)
  { }

  void SetHandlingFunction (std::function<int(EventHandler &)> f) {
     handlingFunction = f;
  }

  // for convenience, if the handling function is a member
  void SetHandlingFunction (int EventHandler::*mf ()) {
      handlingFunction = 
         [mf] (EventHandler & myself) -> int { return myself.*mf (); }
      ;
  }

  int OnEvent () {
     return handlingFunction (*this);
  }

  int HandleEvent ()
  {
     return Num;
  }
};

int main()
{
   EventHandler a(4);
   a.SetHandlingFunction ( [] (EventHandler & h) -> int { return h.HandleEvent (); } );

   // or
   a.SetHandlingFunction (&EventHandler::HandleEvent);

   EventHandler b(a);
   b.Num = 5;

   int aResult = a.OnEvent();
   int bResult = b.OnEvent();

   std::cout << "aResult=" << aResult << "  bResult=" << bResult << '\n';

   return 0;
}

当然,如果你的处理函数always是一个成员函数,你可以简单地将std::function替换为一个指向成员函数的指针。

请注意,您应该在 EventHandler 类的构造函数中正确初始化 handlingFunction 成员,例如通过将其设置为虚拟函数。

【讨论】:

    【解决方案3】:

    我扩展了 user1887915 的答案以允许带参数的函数:

    #include <functional>
    #include <tuple>
    #include <utility>
    #include <memory>
    #include <iostream>
    
    template <typename T>
    class binding_function;
    
    template <typename R, typename... Args, typename SelfType>
    class binding_function<R(SelfType, Args...)> : std::function<R(Args...)>
    {
      using base_function = std::function<R(SelfType, Args...)>;
      using binded_function = std::function<R(Args...)>;
      base_function base;
    
    public:
      binding_function() = default;
    
      template <typename BaseF, typename... TArgs>
      binding_function(BaseF&& f, SelfType t, TArgs&&... args)
        : base(std::forward<BaseF>(f)) {
        rebind(std::forward<SelfType>(t), std::forward<TArgs>(args)...);
      }
    
      template <typename T, typename... TArgs>
      void rebind(T&& t, TArgs&&... args)
      {
        static_cast<binded_function&>(*this) =
          std::bind(base, std::forward<SelfType>(t), std::forward<TArgs>(args)...);
      }
    
      using binded_function::operator();
    };
    
    
    
    class EventHandler
    {
    public:
        int Num;
        binding_function<int(EventHandler, int)> OnEvent;
    
    
        EventHandler (int inNum)
        {
            Num = inNum;
        }
    
        EventHandler (const EventHandler& other)
        {
            Num = other.Num;
            OnEvent = other.OnEvent; //TODO:  Need some way to redirect the std::function to the new instance rather than having the delegate point to the original object's handler.
        }
        int HandleEvent (int value)
        {
            return Num + value;
        }
    };
    int main()
    {
        EventHandler a(4);
    
                    // first binding
        a.OnEvent = {&EventHandler::HandleEvent, a, std::placeholders::_1};
        EventHandler b(a);
        b.Num = 5;
        b.OnEvent.rebind(b, std::placeholders::_1);  // rebinding
    
        int aResult = a.OnEvent(1);
        int bResult = b.OnEvent(1);
    
        //This will print out 4 and 4 instead of 4 and 5 since b is still bound to a's event handler.
        std::cout << "aResult=" << aResult << "  bResult=" << bResult << '\n';
    
        return 0;
    }
    

    【讨论】:

    • 如果我在拷贝构造函数中调用rebind,bind函数又调用拷贝构造函数,导致无限递归。有人知道怎么解决吗?
    【解决方案4】:

    AFAIK 你问的是不可能的,但我认为你可以做一个解决方法:

    class EventHandler
    {
    public:
        int Num;
        std::function<int()> OnEvent;
    
        template <typename Func>
        EventHandler (int inNum, Func on_event)
        {
            Num = inNum;
            OnEvent = [=]() { return (this->*on_event)(); };
        }
    
        EventHandler (const EventHandler& other): EventHandler(other.Num, &EventHandler::HandleEvent) {}
    
        int HandleEvent ()
        {
            return Num;
        }
    };
    
    int main()
    {
        EventHandler a(4, &EventHandler::HandleEvent);
        EventHandler b(a);
        b.Num = 5;
    
        int aResult = a.OnEvent();
        int bResult = b.OnEvent();
    
        //This will print out 4 and 4 instead of 4 and 5 since b is still bound to a's event handler.
        std::cout << "aResult=" << aResult << "  bResult=" << bResult << '\n';
    
        return 0;
    }
    

    这会根据需要打印“aResult=4 bResult=5”。 另外,我认为通过使用更多元编程魔法,我们可以尝试美化语法。

    让我知道这是否适合你。

    【讨论】:

    • 另外,如果它需要能够指向常规函数,那么在使用标签调度的帮助下也应该是可能的。
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2018-01-17
    • 2012-08-22
    • 1970-01-01
    • 2013-01-26
    相关资源
    最近更新 更多