可组合C++功能装饰器
Composable C++ Function Decorators
Python具有函数装饰器的一个非常有用的特性,此外,它允许组合。例如,如果编写一个函数foo
,那么您可以声明您希望记住foo
,但在缓存未命中的情况下也可以重试多次,其中foo
也会引发异常,方法是:
@lru_cache
@retry
def foo(...):
装饰器可组合性允许独立开发foo
和单个功能装饰器等函数,然后根据需要混合它们。如果我们也能在C++这样做(尽可能(,那就太好了。
虽然 StackOverflow 上有几个关于函数装饰器的问题,但由于对装饰函数签名的严格假设,它们似乎都生成了不可组合的问题。 例如,考虑这个问题的优秀最高投票答案。装饰是形式
template <typename R, typename... Args>
std::function<R (Args...)> memo(R (*fn)(Args...)) {
因此,它不能应用于其本身的结果(诚然,对于记忆的特定装饰者使用来说,这不是什么大问题(。
那么,我们如何编写可组合的函数装饰器呢?
创建可组合函数装饰器的另一种方法是使用一组 mixin 类。
它遵循一个最小的工作示例:
#include<iostream>
#include<functional>
#include<utility>
#include<type_traits>
template<class T>
struct LoggerDecoratorA: public T {
template<class U>
LoggerDecoratorA(const U &u): T{u} { }
template<typename... Args>
auto operator()(Args&&... args) const ->
typename std::enable_if<
not std::is_same<
typename std::result_of<T(Args...)>::type,
void
>::value,
typename std::result_of<T(Args...)>::type>::type
{
using namespace std;
cout << "> logger A" << endl;
auto ret = T::operator()(std::forward<Args>(args)...);
cout << "< logger A" << endl;
return ret;
}
template<typename... Args>
auto operator()(Args&&... args) const ->
typename std::enable_if<
std::is_same<
typename std::result_of<T(Args...)>::type,
void
>::value,
typename std::result_of<T(Args...)>::type>::type
{
using namespace std;
cout << "> logger A" << endl;
T::operator()(std::forward<Args>(args)...);
cout << "< logger A" << endl;
}
};
template<class T>
struct LoggerDecoratorB: public T {
template<class U>
LoggerDecoratorB(const U &u): T{u} { }
template<typename... Args>
auto operator()(Args&&... args) const ->
typename std::enable_if<
not std::is_same<
typename std::result_of<T(Args...)>::type,
void
>::value,
typename std::result_of<T(Args...)>::type>::type
{
using namespace std;
cout << "> logger B" << endl;
auto ret = T::operator()(std::forward<Args>(args)...);
cout << "< logger B" << endl;
return ret;
}
template<typename... Args>
auto operator()(Args&&... args) const ->
typename std::enable_if<
std::is_same<
typename std::result_of<T(Args...)>::type,
void
>::value,
typename std::result_of<T(Args...)>::type>::type
{
using namespace std;
cout << "> logger B" << endl;
T::operator()(std::forward<Args>(args)...);
cout << "< logger B" << endl;
}
};
int main() {
std::function<int()> fn = [](){
using namespace std;
cout << 42 << endl;
return 42;
};
std::function<void()> vFn = [](){
using namespace std;
cout << "void" << endl;
};
using namespace std;
decltype(fn) aFn =
LoggerDecoratorA<decltype(fn)>(fn);
aFn();
cout << "---" << endl;
decltype(vFn) bVFn =
LoggerDecoratorB<decltype(vFn)>(vFn);
bVFn();
cout << "---" << endl;
decltype(fn) abFn =
LoggerDecoratorA<LoggerDecoratorB<decltype(fn)>>(fn);
abFn();
cout << "---" << endl;
decltype(fn) baFn =
LoggerDecoratorB<LoggerDecoratorA<decltype(fn)>>(fn);
baFn();
}
我不确定您提到的它实际上解决了什么问题,但请随时要求更改,如果可能的话,我会尝试更新它。
创建可组合函数装饰器的一种方法是放宽对装饰器所取签名的假设。假设我们有
template<class Fn>
struct foo_decorator
{
template<typename ...Args>
auto operator()(Args &&...args) const ->
typename std::result_of<Fn(Args...)>::type;
};
template<class Fn>
foo_decorator<Fn> make_foo(const Fn &fn) {return foo_decorator<Fn>();}
可以看出,make_foo
和foo_decorator
都是按class Fn
参数化的,在这一点上几乎可以是任何东西。因此,例如,它们可以同时采用 lambda 函数或函子。采用的参数(和返回类型(被(编译时(推迟到调用,其中C++函数调用的推导模板参数将根据需要填充其余详细信息。
使用它,这里有一个简单的日志记录装饰器:
#include <type_traits>
#include <cmath>
#include <iostream>
template<class Fn>
struct logger
{
logger(const Fn &fn, const std::string &name) : m_fn(fn), m_name{name}{}
template<typename ...Args>
auto operator()(Args &&...args) const ->
typename std::result_of<Fn(Args...)>::type
{
std::cout << "entering " << m_name << std::endl;
const auto ret = m_fn(std::forward<Args>(args)...);
std::cout << "leaving " << m_name << std::endl;
return ret;
}
private:
Fn m_fn;
std::string m_name;
};
template<class Fn>
logger<Fn> make_log(const Fn &fn, const std::string &name)
{
return logger<Fn>(fn, name);
}
int main()
{
auto fn = make_log([](double x){return std::sin(x);}, "sin");
std::cout << fn(4.0) << std::endl;
}
这是这个装饰器的构建,这是一个重试装饰器的构建,这是它们的组合的构建。
这种方法的一个缺点是装饰器具有依赖于函数签名的状态的情况,例如,原始的记忆情况。可以使用类型擦除来处理这个问题(请参阅此处的构建(,但这有许多缺点,其中之一是概念上可能在编译时捕获的错误现在在运行时捕获(当类型擦除检测到非法使用时(。
- 在执行其他功能的同时播放动画(LED矩阵和Arduino/ESP8266)
- 多态性和功能结合
- 带内存和隔离功能的SQLite
- 在CMakeLists.txt的安装功能中使用.cmake文件有什么用
- 类模板的成员功能的定义在单独的TU中完全专业化
- 有没有一种方法可以创建一个带有哈希表的数据库,该哈希表具有恒定时间查找功能
- 如何在C++中获得"静态纯虚拟"功能?
- 两个文件使用彼此的功能-如何解决
- 我应该实现右值推送功能吗?我应该使用std::move吗
- QML按钮点击功能执行顺序
- 无法理解此 return 语句的功能,没有它就会发生运行时错误
- 有没有可能有一个只有ADL才能找到的非好友功能
- 功能样式转换从 'int' 到 'ItemType' 的匹配转换
- 文件系统:复制功能的速度秘诀是什么
- 在用于格式4的arm模拟器中实现功能时的一个问题
- 如何在Directwrite中获得给定字体的可用OpenType功能
- 对可变参数使用声明.如何选择正确的功能
- 询问在设计我的手臂模拟器功能表示格式1
- 功能原型的目的
- 这里在 Linux 中具有"CreatePipe"和"CreateProcessW"功能吗?