首页 文章

将具有任意参数和占位符的函数存储在类中,稍后再调用它

提问于
浏览
2

所以我正在创建一种事件处理程序,如果你愿意,我正在编写一个“事件监听器包装器” .

基本思想是这样的:当你想订阅一个事件时,你创建一个应该在事件触发时调用的函数 . < - 已经完成了(有点,我会解释)

您将此侦听器函数放入包装器以将该函数传递给调度程序 .

调度程序获取一个事件,找到侦听器的包装器,并使用事件设置的参数值调用基础函数 .

只要听众都只接受我的 EventBase 类的一个参数,我已经有了一些工作 . 然后我必须在传递监听器的正确事件中键入强制转换 .

我想要的是我的监听器函数具有“任何”类型的参数,并以一种允许我用我想要的任何参数调用它的方式存储函数,具体取决于触发的事件 . 每个监听器功能只会接收一种类型的事件,或者它自己的事件 . 这将允许我不必在每个侦听器中键入强制转换每个事件,而是传递正确的事件 .

我发现这个包装器的代码几乎是完美的,有一些小问题,我可以在下面解释 .

代码@hmjd:

#include <iostream>
#include <string>
#include <functional>
#include <memory>

void myFunc1(int arg1, float arg2)
{
    std::cout << arg1 << ", " << arg2 << '\n';
}
void myFunc2(const char *arg1)
{
    std::cout << arg1 << '\n';
}

class DelayedCaller
{
public:
    template <typename TFunction, typename... TArgs>
    static std::unique_ptr<DelayedCaller> setup(TFunction&& a_func,
                                                TArgs&&... a_args)
    {
        return std::unique_ptr<DelayedCaller>(new DelayedCaller(
            std::bind(std::forward<TFunction>(a_func),
                      std::forward<TArgs>(a_args)...)));
    }
    void call() const { func_(); }

private:
    using func_type = std::function<void()>;
    DelayedCaller(func_type&& a_ft) : func_(std::forward<func_type>(a_ft)) {}
    func_type func_;
};

int main()
{
    auto caller1(DelayedCaller::setup(&myFunc1, 123, 45.6));
    auto caller2(DelayedCaller::setup(&myFunc2, "A string"));

    caller1->call();
    caller2->call();

    return 0;
}

我在这里做的第一件事是我必须用 std::shared_ptr 替换 std::unique_ptr . 不知道为什么真的 . 这几乎可行 . 在我的用例中,我需要存储一个方法函数(意味着bind需要传递包含方法对象?),并且在存储函数时我不知道参数值是什么,那就是事件来决定 . 所以我的调整如下:

class DelayedCaller
{
public:

    template <typename TFunction, typename TClass>
    static std::shared_ptr<DelayedCaller> setup(TFunction&& a_func,
                                                TClass && a_class)
    {

        auto func = std::bind(std::forward<TFunction>(a_func),
                              std::forward<TClass>(a_class),
                              std::placeholders::_1);

        return std::shared_ptr<DelayedCaller>(new DelayedCaller(func));
    }

    template <typename T>
    void call( T v ) const { func_(v); }

private:
    using func_type = std::function<void(  )>;
    DelayedCaller(func_type&& a_ft) : func_(std::forward<func_type>(a_ft)) {}
    func_type func_;
};

为了测试,我删除了参数包,并将其替换为带有该函数的类对象的直接参数 . 我还为bind赋予了1个参数的占位符(以后最好用 void call() 函数替换) .

它是这样创建的:

eventManager->subscribe(EventDemo::descriptor, DelayedCaller::setup(
                                &AppBaseLogic::getValueBasic,
                                this
                                ));

问题是:在这一行:

return std::shared_ptr<DelayedCaller>(new DelayedCaller(func));

我得到“没有匹配函数来调用'DelayedCaller :: DelayedCaller(std :: _ Bind(AppBaseLogic *,std :: _ Placeholder <1>)>&)'return std :: shared_ptr(new DelayedCaller(func));”

这仅在使用 placeholder::_1 时发生 . 如果我用正确类型的已知值替换它,它就可以工作,除了函数被调用时当然没有任何有用的数据 .

所以,我想我需要一种方法来存储具有占位符的函数,我不知道它的类型?

如果我得到错误的名字,请原谅我 . 我是新手,我过去几天才开始学习它 .

编辑:

好的,所以我只是更新为什么我需要存储这样的功能 . 我的事件调度程序中有一张 Map ,如下所示:

std::map< const char*, std::vector<DelayedCaller> > _observers;

我希望能够在“Delayed Caller”中调用这样的函数:

void Dispatcher::post( const EventBase& event ) const
{
    // Side Note: I had to do this instead of map.find() and map.at() because 
    // passing a "const char*" was not evaluating as equal to event.type() even 
    // though event.type() is also a const char*. So instead I am checking it 
    // myself, which is fine because it gives me a little more control.

    std::string type(event.type());
    for( auto const &x : _observers ) {
        std::string type2(x.first);
        if ( type == type2 ) {
            auto&& observers = x.second;

            for( auto&& observer : observers ) {
                // event may be any descendant of EventBase.
                observer.slot->call(event);
            }
            break;
        }
    }
}

我的听众目前看起来像这样:

void AppBaseLogic::getValue(const EventBase &e) {
    const EventDemo& demoEvent = static_cast<const EventDemo&>( e );
    std::cout << demoEvent.type();
}

我试图存储每个函数,以便参数可能如下所示:

void AppBaseLogic::getValue(const EventAnyDescendant &e) {
    std::cout << e.type();
}

希望这会有所帮助 . 谢谢大家花时间帮我解决这个问题 .

关于lambdas的旁注:有人建议他们,我知道他们是什么或如何使用它们,但我会对它们做一些研究,所以看看是否会更有意义 . 我担心他们的可维护性,尽管我已经看到了 .

5 回答

  • 0

    你的 DelayedCaller 正在做什么还不太清楚 . 如果你重构代码并摆脱它,你会得到这样的:

    auto c1 = []() {myFunc1(123, 45.6);}; // or use bind, the result is exactly the same
    auto c2 = []() {myFunc2("A string");};
    
    vector<function<void()>> v {c1, c2};
    v[0]();
    v[1](); // ok
    

    现在,如果您尝试在此版本中引入占位符修改,则很明显为什么它首先不起作用:

    auto cSome = [](???) {getValueBasic(???)};
    

    你用什么替换 ???

    getValueBasic 接受某种特定类型的参数,它会泄漏到 cSome 签名中 . 无论你将它包装多少个模板包装器,它都会泄漏到每个包装器的签名中,包括最外面的包装器 . bindstd::placeholders 不是一个能让它不发声的魔杖 .

    换句话说,如果你不知道你的功能类型,你就不能称之为它(有点明显,不是吗?)

    键入擦除签名并使所有可调用符合相同类型的一种方法是进行类型检查并对它们进行类型转换(a.k.a. dynamic_cast ) . 另一个是双重派遣 . 这两种方法都是访客相同概念的不同形式 . Google "the visitor pattern"了解更多信息 .

  • 0

    也许这适合你 . 用c 11

    #include <iostream>                                                                                                                                                                                                 
    #include <functional>
    #include <vector>
    
    namespace test
    {
    
    
      std::vector<std::function<void()>> listeners;
    
      template<typename F, typename... Args>
      void add_listener(F call, Args&& ...args )
      {   
        std::cout << "callback_dispatcher>" << __PRETTY_FUNCTION__ << "enter <<< " << std::endl;                                                 
        auto invoke_me = [=]()mutable{
          call(std::move(args)...);
        };  
        listeners.push_back(invoke_me);
      }   
    
      void dispatch_all()
      {
        for(auto func: listeners)
        {   
           func();
        }   
      }   
    
    }
    
    int main()
    {
      std::cout << "Main entered..." << std::endl;
    
    
      test::add_listener(
        [](int a)
        {   
          std::cout << "void(int) lambda dispatched with a = " << a << std::endl;
        },  
        5   
      );  
      test::add_listener(
        [](int a, std::string str)
        {   
          std::cout << "void(int, string) lambda dispatched with a = " << a << ", str = " << str << std::endl;
        },  
        10, "Hello World!"
      );  
    
      test::dispatch_all();
    
      std::cout << "Main exited..." << std::endl;
    }
    

    输出:

    Main entered...
    callback_dispatcher>void test::add_listener(F, Args&& ...) [with F = main()::<lambda(int)>; Args = {int}]enter <<< 
    callback_dispatcher>void test::add_listener(F, Args&& ...) [with F = main()::<lambda(int, std::__cxx11::string)>; Args = {int, const char (&)[13]}]enter <<< 
    void(int) lambda dispatched with a = 5
    void(int, string) lambda dispatched with a = 10, str = Hello World!
    Main exited...
    

    请参阅SO_QUESTION,了解为什么在扩展lambda中的args时使用mutable和std :: move .

  • 0

    看看std :: bind和std :: mem_fn

    c = 11版本能够在参数列表上进行各种巧妙的转换,以生成类似函数的对象 .

    当然,Lambdas提供了更大的灵活性,你可以将它们混合起来 .

  • 1

    我在 DelayedCaller 的修改后的(方法和占位符)版本中看到了两个主要问题

    (1)现在 call() 接收一个参数(类型 T ),因此用一个参数调用 func_() ;但 func_() 仍然定义为 std::function<void()> 类型,因此无法接收参数[这一点是您"no matching function"错误的原因]

    (2)如果你模板化 call() ,接收类型为 T 的参数,则需要将 func_ 的类型变为 std::function<void(T)> ;所以你必须模仿全班 .

    考虑到计数(1)和(2),并保持 std::unique_ptr ,我已将 DelayedCaller 重写为 dcM1M 为"method", 1 为"1 parameter")

    template <typename T>
    class dcM1
     {
       public:
          template <typename TFunction, typename TClass>
          static std::unique_ptr<dcM1> setup (TFunction && a_func,
                                              TClass && a_class)
           {
             auto func = std::bind(std::forward<TFunction>(a_func),
                                   std::forward<TClass>(a_class),
                                   std::placeholders::_1);
    
             return std::unique_ptr<dcM1>(new dcM1(func));
           }
    
          void call( T v ) const
           { func_(v); }
    
       private:
          using func_type = std::function<void(T)>;
    
          dcM1(func_type && a_ft) : func_(std::forward<func_type>(a_ft))
           { }
    
          func_type func_;
     };
    

    并可以如下使用

    auto cm1f = dcM1<int>::setup(&foo::func, &f);
       auto cm1b = dcM1<long>::setup(&bar::func, &b);
    
       cm1f->call(0);
       cm1b->call(1L);
    

    以下是一个完整的工作示例

    #include <iostream>
    #include <string>
    #include <functional>
    #include <memory>
    
    void myFunc1 (int arg1, float arg2)
     { std::cout << arg1 << ", " << arg2 << '\n'; }
    
    void myFunc2 (char const * arg1)
     { std::cout << arg1 << '\n'; }
    
    class dcVoid
     {
       public:
          template <typename TFunction, typename... TArgs>
          static std::unique_ptr<dcVoid> setup (TFunction && a_func,
                                                       TArgs && ... a_args)
           {
             return std::unique_ptr<dcVoid>(new dcVoid(
                   std::bind(std::forward<TFunction>(a_func),
                             std::forward<TArgs>(a_args)...)));
           }
    
          void call() const
           { func_(); }
    
       private:
          using func_type = std::function<void()>;
    
          dcVoid(func_type && a_ft) : func_(std::forward<func_type>(a_ft))
           { }
    
          func_type func_;
     };
    
    template <typename T>
    class dcM1
     {
       public:
          template <typename TFunction, typename TClass>
          static std::unique_ptr<dcM1> setup (TFunction && a_func,
                                              TClass && a_class)
           {
             auto func = std::bind(std::forward<TFunction>(a_func),
                                   std::forward<TClass>(a_class),
                                   std::placeholders::_1);
    
             return std::unique_ptr<dcM1>(new dcM1(func));
           }
    
          void call( T v ) const
           { func_(v); }
    
       private:
          using func_type = std::function<void(T)>;
    
          dcM1(func_type && a_ft) : func_(std::forward<func_type>(a_ft))
           { }
    
          func_type func_;
     };
    
    struct foo
     { void func (int i) { std::cout << "foo func: " << i << std::endl; } };
    
    struct bar
     { void func (long l) { std::cout << "bar func: " << l << std::endl; } };
    
    int main ()
     {
       auto cv1 = dcVoid::setup(&myFunc1, 123, 45.6);
       auto cv2 = dcVoid::setup(&myFunc2, "A string");
    
       foo f;
       bar b;
    
       auto cm1f = dcM1<int>::setup(&foo::func, &f);
       auto cm1b = dcM1<long>::setup(&bar::func, &b);
    
       cv1->call();
       cv2->call();
    
       cm1f->call(0);
       cm1b->call(1L);
     }
    
  • 0

    好的,所以我知道这已经有一段时间了 . 我一直在研究不同的事件模式,试图找到更接近我所追求的东西 . 在倾注了所有内容之后,并根据那些留下评论的人的建议,我决定使用信号/插槽模式,可能是C中使用最广泛的事件模式 . 接近它的方法是让我的所有"logic classes"(无论是用于gui还是用于计算)保留对第三个"signal event holder class"的引用,为了简单起见,我将其称为事件代理 . 这和我能得到的一样好 . 您可能希望拥有的任何事件都可以添加到此类中,并且可以通过引用事件代理从任何类访问和调用它 . 我找到了一个由Simon Schneegans制作的非常好的信号类,但我正在积极地寻找/学习如何做出更好的东西(线程安全,可能更快?) . 如果有人有兴趣/寻找像我一样的帮助,你可以在这里找到我的超级基本测试用例:https://github.com/Moonlight63/QtTestProject

    谢谢!

相关问题