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

时间:2013-03-18 17:49:31

标签: c++ delegates function-pointers function-object

我想在集合中存储具有类似签名的函数,以执行以下操作:

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());

3 个答案:

答案 0 :(得分:9)

前提:

你提出的设计是可行的,但使用常规函数指针会对你可以注册的回调类型有很大的限制,虽然功能更强大,但基于从固定接口继承的方法更加冗长,需要更多工作客户端定义回调。

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

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


基于std :: function&lt;&gt;

的解决方案

让我们介绍几个简单的类,并为一些具体的例子做好准备。这里,orderid并且包含多个item的内容。 item描述了每个type(为简单起见,这里可以是书籍DVD)和name

#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 alias

class 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, "TC++PL, 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, "TC++PL, 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()的结果注册为回调。为了通过示例演示这一点,让我们再引入一个类person

#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;
};

班级person有一个成员函数receive_order()。在某个receive_order()对象上调用person可以模拟特定order已发送到person的事实。

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

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";
}

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

Person

此程序的输出显示在this live example中。

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

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显示相应的输出。


超越std :: function&lt;&gt; (Boost.Signals2)

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

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

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

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

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();
}

像往常一样,上面的程序是live example

答案 1 :(得分:1)

您可能希望查看std::function,您的矢量将如下所示:

std::vector< std::function< void( Order ) > > functions;

但请注意std::function的开销很小。对于实例,请删除new

function.push_back(GiveStupidOrdersToJohn());

答案 2 :(得分:0)

Boost.Signal完全解决了您的问题。你应该看一下。除非你有特殊要求。特别是boost.signal和boost.function和/或std :: function 使用类型擦除技术。因此,您可以使用指定签名的可调用事物向量。如果您的实体是普通的C函数(如您的示例中所示)或函数对象或成员函数,则无关紧要。你可以混合所有这些。