是否有一种惯用的方法来在C中创建委托集合?

是否有一种惯用的方法来在C中创建委托集合?,第1张

概述我想在集合中存储具有类似签名的函数,以执行以下 *** 作: f(vector<Order>& orders, vector<Function>& functions) { foreach(process_orders in functions) process_orders(orders);} 我想到了函数指针: void GiveCoolOrdersToBob(Order);void Gi 我想在集合中存储具有类似签名的函数,以执行以下 *** 作:
f(vector<Order>& orders,vector<Function>& functions) {    foreach(process_orders in functions) process_orders(orders);}

我想到了函数指针:

voID GiveCoolOrdersToBob(Order);voID GiveStupIDOrdersToJohn(Order);typedef voID (*Function)(Order);vector<Function> functions;functions.push_back(&GiveStupIDOrdersToJohn);functions.push_back(&GiveCoolOrdersToBob);

或多态函数对象:

struct IOrderFunction {    virtual voID operator()(Order) = 0;}struct GiveCoolOrdersToBob : IOrderFunction {    ...}struct GiveStupIDOrdersToJohn : IOrderFunction {    ...}vector<IOrderFunction*> functions;functions.push_back(new GiveStupIDOrdersToJohn());functions.push_back(new GiveCoolOrdersToBob());
解决方法 前提:

您提出的设计将起作用,但使用常规函数指针将极大地限制您可以注册的回调类型,虽然功能更强大,但基于从固定接口继承的方法更加冗长,并且需要为客户端执行更多工作定义回调.

在这个答案中,我将首先展示一些如何将std::function用于此目的的示例.这些例子几乎可以说明一切,展示了如何以及为什么使用std :: function带来了优势,而不是你提出的那种解决方案.

但是,基于std :: function的简单方法也会有自己的局限性,我将列出.这就是为什么我最终建议你看看Boost.Signals2:它是一个非常强大且易于使用的库.我将在本回答的最后给出Boost.Signals2.希望首先了解一个基于std :: function的简单设计,以便您以后更容易掌握信号和插槽的更复杂方面.

基于std :: function<>的解决方案

让我们介绍几个简单的类,并为一些具体的例子做好准备.这里,订单是具有ID并包含多个项目的东西.每个项目都由一个类型(为简单起见,它可以是一本书,一个DVD)和一个名称描述:

#include <vector>#include <memory>#include <string>struct item // A very simple data structure for modeling order items{    enum type { book,dvd };    item(type t,std::string const& s) : itemType(t),name(s) { }    type itemType; // The type of the item    std::string name; // The name of the item};struct order // An order has an ID and contains a certain number of items{    order(int ID) : ID(ID) { }    int get_ID() const { return ID; }    std::vector<item> const& get_items() const { return items; }    voID add_item(item::type t,std::string const& n)    { items.emplace_back(t,n); }private:    int ID;    std::vector<item> items;};

我要概述的解决方案的核心是以下类order_repository,以及std :: function的内部用法,用于保存客户端注册的回调.

回调可以通过register_callback()函数注册,并且(非常直观地)通过unregister_callback()函数注册,方法是在注册时提供registered_callback()返回的cookie:

该函数具有用于放置订单的place_order()函数,以及用于触发所有订单处理的process_order()函数.这将导致所有已注册的处理程序按顺序调用.每个处理程序都接收对相同的已下订单向量的引用:

#include <functional>using order_ptr = std::shared_ptr<order>; // Just a useful type aliasclass order_repository // Collects orders and registers processing callbacks{public:    typedef std::function<voID(std::vector<order_ptr>&)> order_callback;    template<typename F>    size_t register_callback(F&& f)    { return callbacks.push_back(std::forward<F>(f)); }    voID place_order(order_ptr o)    { orders.push_back(o); }    voID process_all_orders()    { for (auto const& cb : callbacks) { cb(orders); } }private:    std::vector<order_callback> callbacks;    std::vector<order_ptr> orders;};

这个解决方案的优势来自于使用std :: function来实现类型擦除和allow encapsulating any kind of callable object.

我们将用于生成和下订单的以下辅助函数完成设置(它只创建四个订单并为每个订单添加几个项目):

voID generate_and_place_orders(order_repository& r){    order_ptr o = std::make_shared<order>(42);    o->add_item(item::book,"TC++PL,4th Edition");    r.place_order(o);    o = std::make_shared<order>(1729);    o->add_item(item::book,4th Edition");    o->add_item(item::book,"C++ Concurrency in Action");    r.place_order(o);    o = std::make_shared<order>(24);    o->add_item(item::dvd,"2001: A Space Odyssey");    r.place_order(o);    o = std::make_shared<order>(9271);    o->add_item(item::dvd,"The Big Lebowski");    o->add_item(item::book,"C++ Concurrency in Action");    o->add_item(item::book,4th Edition");    r.place_order(o);}

现在让我们看看我们可以提供哪种回调.对于启动器,让我们有一个打印所有订单的常规回调函数:

voID print_all_orders(std::vector<order_ptr>& orders){    std::cout << "Printing all the orders:\n=========================\n";    for (auto const& o : orders)    {        std::cout << "\torder #" << o->get_ID() << ": " << std::endl;        int cnt = 0;        for (auto const& i : o->get_items())        {            std::cout << "\t\titem #" << ++cnt << ": ("                      << ((i.itemType == item::book) ? "book" : "dvd")                      << "," << "\"" << i.name << "\")\n";        }    }    std::cout << "=========================\n\n";}

还有一个使用它的简单程序:

int main(){    order_repository r;    generate_and_place_orders(r);    // Register a regular function as a callback...    r.register_callback(print_all_orders);    // Process the order! (Will invoke all the registered callbacks)    r.process_all_orders();}

这是显示该程序输出的live example.

相当合理的是,您不仅限于注册常规函数:任何可调用对象都可以注册为回调,包括包含一些状态信息的仿函数.让我们将上述函数重写为一个函子,它可以打印与上面函数print_all_orders()相同的详细订单列表,或者不包含订单商品的较短摘要:

struct print_all_orders{    print_all_orders(bool detailed) : printDetails(detailed) { }    voID operator () (std::vector<order_ptr>& orders)    {        std::cout << "Printing all the orders:\n=========================\n";        for (auto const& o : orders)        {            std::cout << "\torder #" << o->get_ID();            if (printDetails)            {                std::cout << ": " << std::endl;                int cnt = 0;                for (auto const& i : o->get_items())                {                    std::cout << "\t\titem #" << ++cnt << ": ("                              << ((i.itemType == item::book) ? "book" : "dvd")                              << "," << "\"" << i.name << "\")\n";                }            }            else { std::cout << std::endl; }        }        std::cout << "=========================\n\n";    }private:    bool printDetails;};

以下是如何在小型测试程序中使用它:

int main(){    using namespace std::placeholders;    order_repository r;    generate_and_place_orders(r);    // Register one particular instance of our functor...    r.register_callback(print_all_orders(false));    // Register another instance of the same functor...    r.register_callback(print_all_orders(true));    r.process_all_orders();}

这是this live example中显示的相应输出.

由于std :: function提供的灵活性,我们还可以将std :: bind()的结果注册为回调.为了通过一个例子证明这一点,让我们介绍一个更多的类人:

#include <iostream>struct person{   person(std::string n) : name(n) { }   voID receive_order(order_ptr spOrder)   { std::cout << name << " received order " << spOrder->get_ID() << std::endl; }private:   std::string name;};

类人员具有成员函数receive_order().在特定人物对象上调用receive_order()可以模拟特定订单已交付给该人的事实.

我们可以使用上面的类定义来注册一个回调函数,该函数将所有订单分派给一个人(可以在运行时确定!):

voID give_all_orders_to(std::vector<order_ptr>& orders,person& p){    std::cout << "dispatching orders:\n=========================\n";    for (auto const& o : orders) { p.receive_order(o); }    orders.clear();    std::cout << "=========================\n\n";}

此时我们可以编写以下程序,它注册两个回调:用于打印我们之前使用过的订单的相同函数,以及用于将订单分派给Person的某个实例的上述函数.我们是这样做的:

int main(){    using namespace std::placeholders;    order_repository r;    generate_and_place_orders(r);    person alice("alice");    r.register_callback(print_all_orders);    // Register the result of binding a function's argument...    r.register_callback(std::bind(give_all_orders_to,_1,std::ref(alice)));    r.process_all_orders();}

该程序的输出显示在this live example中.

当然,人们可以使用lambdas作为回调.以下程序以前面的程序为基础,演示了一个lambda回调的用法,该回调将小订单发送给一个人,大订单发送给另一个人:

int main(){    order_repository r;    generate_and_place_orders(r);    person alice("alice");    person bob("bob");    r.register_callback(print_all_orders);    r.register_callback([&] (std::vector<order_ptr>& orders)    {        for (auto const& o : orders)        {            if (o->get_items().size() < 2) { bob.receive_order(o); }            else { alice.receive_order(o); }        }        orders.clear();    });    r.process_all_orders();}

this live example再次显示相应的输出.

超越std :: function<> (Boost.Signals2)

上述设计相对简单,非常灵活,易于使用.但是,它有许多事情不允许这样做:

>它不允许轻松冻结并恢复将事件调度到特定回调;
>它不会将相关回调集合封装到事件中
类;
>它不允许对回调进行分组并对它们进行排序;
>它不允许回调返回值;
>它不允许组合这些返回值.

所有这些功能以及许多其他功能都由完整的库提供,例如Boost.Signals2,您可能需要查看这些库.熟悉上述设计,您将更容易理解它的工作原理.

例如,这是你如何定义信号并注册两个简单的回调,并通过调用信号的调用 *** 作符(来自链接的文档页面)来调用它们:

struct Hello{    voID operator()() const    {        std::cout << "Hello";    }};struct World{    voID operator()() const    {        std::cout << ",World!" << std::endl;    }};int main(){    boost::signals2::signal<voID ()> sig;    sig.connect(Hello());    sig.connect(World());    sig();}

像往常一样,这是上述程序的live example.

总结

以上是内存溢出为你收集整理的是否有一种惯用的方法来在C中创建委托集合?全部内容,希望文章能够帮你解决是否有一种惯用的方法来在C中创建委托集合?所遇到的程序开发问题。

如果觉得内存溢出网站内容还不错,欢迎将内存溢出网站推荐给程序员好友。

欢迎分享,转载请注明来源:内存溢出

原文地址: http://outofmemory.cn/langs/1254872.html

(0)
打赏 微信扫一扫 微信扫一扫 支付宝扫一扫 支付宝扫一扫
上一篇 2022-06-07
下一篇 2022-06-07

发表评论

登录后才能评论

评论列表(0条)

保存