Flu*_*ffy 5 c++ api-design c++11
我正在研究一个我希望在调用者方面尽可能通用的API.主要设计思想是提供信号/插槽类型的实现,允许API的用户订阅一组给定的事件,并将用户定义的回调附加到它们.
公共接口看起来像这样
RetCallback subscribe(EventEnum& ev, std::function<void(void*)> fn) const;
::注意void(void*)这里的签名.EventEnum在公共头文件中给出,以及类型定义.
然后,API的内部工作将通过notify方法通知其订阅的事件观察者,并提供数据以转发给客户端:
void dummyHeavyOperation() const {
std::this_thread::sleep_for(2s);
std::string data = "I am working very hard";
notify(EventEnum::FooEvent, &data);
}
Run Code Online (Sandbox Code Playgroud)
客户端订阅并将数据转换为(文档化)类型,如下所示:
auto subscriber = Controller->subscribe(EventEnum::FooEvent, callback);
Run Code Online (Sandbox Code Playgroud)
哪里
void callback(void* data) {
auto* myData = (std::string*) data;
std::cout << "callback() with data=" << *myData << std::endl;
/// Do things
}
Run Code Online (Sandbox Code Playgroud)
这是一个合理的设计还是不赞成?您经验丰富的现代C++开发人员的想法告诉您什么?
[编辑]
我还应该补充说,API是作为在运行时加载的共享库提供的.所以任何编译时耦合(以及代码生成,除非我弄错了)都不在桌面上
谢谢!
C++ API设计:使用void*一个坏主意?
是.
要实现等效的API,您应该使用std::any,这是类型擦除的检查版本.std::any从目前的C++ 17标准版本开始,它仅在标准库中可用.如果您没有C++ 17(或者不希望API的用户依赖于C++ 17),那么您可以使用非标准实现.
另一种方法是根本不删除参数类型,而是一直使用模板.有关此类回调API的示例,请参阅Boost Signals
是的,void*这是一个坏主意。当涉及的类型来自您而不是用户时更是如此!
如果不同的事件将不同类型的数据传递给客户端,那么强制类型安全对于用户来说非常有用。它可以防止意外传递需要字符串但使用双精度值调用的回调之类的情况。您希望您的 API 难以被滥用。
例如,您可以这样做:
template<class T>
RetCallback subscribe(EventEnum& ev, std::function<void(T)> fn) const;
Run Code Online (Sandbox Code Playgroud)
订阅者将在调用站点拼写出类型:
auto subscriber = Controller->subscribe<std::string>(EventEnum::FooEvent, callback);
Run Code Online (Sandbox Code Playgroud)
然后,您可以检查该回调签名subscribe是否EventNum正常,或者您甚至可以(取决于您拥有的事件和回调数据类型的数量)为每个回调数据类型设置不同的EventNum 类型,这样就不可能在不匹配的情况下调用订阅事件类型和回调签名,如下所示: https: //godbolt.org/g/7xTGiM
notify必须以与 类似的方式完成subscribe。
这样,任何不匹配要么是不可能的(即编译器强制执行的),要么立即在 API 中捕获,而不是稍后在用户代码中导致意外的转换失败。
编辑:正如评论中所讨论的,如果可以将用户固定在编译时事件值上,您甚至可以在事件编号本身上进行模板: https: //godbolt.org/g/9NYVh3