Ant*_*yko 6 c++ delegates function-pointers function-object
我想在集合中存储具有类似签名的函数,以执行以下操作:
f(vector<Order>& orders, vector<Function>& functions) {
foreach(process_orders in functions) process_orders(orders);
}
Run Code Online (Sandbox Code Playgroud)
我想到了函数指针:
void GiveCoolOrdersToBob(Order);
void GiveStupidOrdersToJohn(Order);
typedef void (*Function)(Order);
vector<Function> functions;
functions.push_back(&GiveStupidOrdersToJohn);
functions.push_back(&GiveCoolOrdersToBob);
Run Code Online (Sandbox Code Playgroud)
或多态函数对象:
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());
Run Code Online (Sandbox Code Playgroud)
您提出的设计将起作用,但使用常规函数指针将极大地限制您可以注册的回调类型,虽然功能更强大,但基于从固定接口继承的方法更加冗长,并且需要为客户端执行更多工作定义回调.
在这个答案中,我将首先展示一些如何std::function用于此目的的示例.这些例子几乎可以说明一切,展示使用的方式和原因std::function带来的优势,而不是您概述的解决方案.
然而,基于的一种天真的方法std::function也将有其自身的局限性,我将列出.这就是为什么我最终建议你看看Boost.Signals2:它是一个非常强大且易于使用的库.我将在本回答的最后给出Boost.Signals2.希望首先了解一个简单的设计std::function将使您更容易掌握信号和插槽的更复杂方面.
让我们介绍几个简单的类,并为一些具体的例子做好准备.这里,a order是一个id包含几个items的东西.每个item都用a来描述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;
};
Run Code Online (Sandbox Code Playgroud)
我要概述的解决方案的核心是以下类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;
};
Run Code Online (Sandbox Code Playgroud)
该解决方案的优势来自于使用std::function实现类型擦除并允许封装任何类型的可调用对象.
我们将用于生成和下订单的以下辅助函数完成设置(它只创建四个订单并为每个订单添加几个项目):
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);
}
Run Code Online (Sandbox Code Playgroud)
现在让我们看看我们可以提供哪种回调.对于启动器,让我们有一个打印所有订单的常规回调函数:
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";
}
Run Code Online (Sandbox Code Playgroud)
还有一个使用它的简单程序:
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();
}
Run Code Online (Sandbox Code Playgroud)
这是显示该程序输出的实例.
相当合理的是,您不仅限于注册常规函数:任何可调用对象都可以注册为回调,包括包含一些状态信息的仿函数.让我们将上述函数重写为一个函子,它可以打印与上面函数相同的详细订单列表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;
};
Run Code Online (Sandbox Code Playgroud)
以下是如何在小型测试程序中使用它:
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();
}
Run Code Online (Sandbox Code Playgroud)
以下是此实例中显示的相应输出.
由于提供的灵活性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;
};
Run Code Online (Sandbox Code Playgroud)
类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";
}
Run Code Online (Sandbox Code Playgroud)
此时我们可以编写以下程序,它注册两个回调:用于打印我们之前使用过的订单的相同函数,以及用于将订单分派到某个实例的上述函数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();
}
Run Code Online (Sandbox Code Playgroud)
此程序的输出显示在此实例中.
当然,人们可以使用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();
}
Run Code Online (Sandbox Code Playgroud)
再一次,这个实例显示了相应的输出.
上述设计相对简单,非常灵活,易于使用.但是,它有许多事情不允许这样做:
所有这些功能以及许多其他功能都由完整的库提供,例如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();
}
Run Code Online (Sandbox Code Playgroud)
像往常一样,这是上述程序的实例.