gpt4 book ai didi

c++ - 有没有一种惯用的方法来在 C++ 中创建一组委托(delegate)?

转载 作者:塔克拉玛干 更新时间:2023-11-02 23:19:17 25 4
gpt4 key购买 nike

我想在一个集合中存储具有相似签名的函数来做这样的事情:

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

最佳答案

前提:
您提出的设计将有效,但使用常规函数指针将大大限制您可以注册的回调类型,尽管更强大,但基于从固定接口(interface)继承的方法更加冗长,并且需要客户端进行更多工作定义回调。
在这个答案中,我将首先展示一些如何使用 std::function 的示例。以此目的。这些例子几乎不言自明,展示了如何以及为什么使用 std::function与您概述的那种解决方案相比,带来了优势。
然而,一种基于 std::function 的幼稚方法也将有其自身的局限性,我将列出。这就是为什么我最终建议你看看 Boost.Signals2 :它是一个非常强大且易于使用的库。我将在本答案的末尾讨论 Boost.Signals2。希望了解基于 std::function 的简单设计first 将使您在以后更容易掌握信号和插槽的更复杂方面。

基于 std::function<> 的解决方案
让我们介绍几个简单的类,并为一些具体的例子做准备。在这里,一个 order是具有 id 的东西并包含多个 item s。每个itemtype 描述(为简单起见,这里可以是书或 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;
};
我将要概述的解决方案的核心是以下类(class) order_repository ,及其内部用法 std::function保存客户端注册的回调。
回调可以通过 register_callback() 注册函数,并且(非常直观地)通过 unregister_callback() 取消注册通过提供 registered_callback() 返回的 cookie 来实现功能注册时:
该函数比具有 place_order()用于下订单的功能,以及 process_order()触发所有订单处理的函数。这将导致所有注册的处理程序被顺序调用。每个处理程序接收对相同下单 vector 的引用:
#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;
};
类(class) person有一个成员函数 receive_order() .调用 receive_order()某某 person对象模拟特定 order 的事实已经送到了 person .
我们可以使用上面的类定义来注册一个回调函数,该函数将所有订单分派(dispatch)给一个人(可以在运行时确定!):
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 回调的用法,该回调将小订单分派(dispatch)给一个人,将大订单分派(dispatch)给另一个人:
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)
上面的设计比较简单,比较灵活,使用方便。但是,它不允许做很多事情:
  • 它不允许轻松卡住和恢复将事件分派(dispatch)到特定回调;
  • 它不会将一组相关的回调封装到一个事件中
    类(class);
  • 它不允许对回调进行分组和排序;
  • 它不允许回调返回值;
  • 它不允许组合这些返回值。

  • 所有这些功能以及许多其他功能都由成熟的库提供,例如 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++ 中创建一组委托(delegate)?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/15483505/

    25 4 0
    Copyright 2021 - 2024 cfsdn All Rights Reserved 蜀ICP备2022000587号
    广告合作:1813099741@qq.com 6ren.com