c++ - C++17 中的 Python 风格装饰器
问题描述
我正在使用最新的可用 C++ 技术制作类似 python 的装饰器。我已经在这里看到了一些解决方案(类似 Python 的 C++ 装饰器),但我想知道它是否可以做得更好。在其他人的帮助下(从 lambda 构造 std::function 参数)我想出了以下解决方案。
template<typename TWrapped>
auto DurationAssertDecorator(const std::chrono::high_resolution_clock::duration& maxDuration, TWrapped&& wrapped)
{
return [wrapped = std::forward<TWrapped>(wrapped), maxDuration](auto&&... args)
{
const auto startTimePoint = std::chrono::high_resolution_clock::now();
static_assert(std::is_invocable<TWrapped, decltype(args)...>::value, "Wrapped object must be invocable");
if constexpr (!(std::is_void<decltype(wrapped(std::forward<decltype(args)>(args)...))>::value))
{
// return by reference will be here not converted to return by value?
//auto result = wrapped(std::forward<decltype(args)>(args)...);
decltype(wrapped(std::forward<decltype(args)>(args)...)) result = wrapped(std::forward<decltype(args)>(args)...);
const auto endTimePoint = std::chrono::high_resolution_clock::now();
const auto callDuration = endTimePoint - startTimePoint;
assert(callDuration <= maxDuration);
return result;
}
else
{
wrapped(std::forward<decltype(args)>(args)...);
const auto endTimePoint = std::chrono::high_resolution_clock::now();
const auto callDuration = endTimePoint - startTimePoint;
assert(callDuration <= maxDuration);
}
};
}
我不会故意使用下面的“auto”来确保返回类型是我所期望的(或至少兼容)。
我可以将它与任何可调用对象一起使用:无状态 lambda、有状态 lambda、结构函子、函数指针、std::function
std::function<double(double)> decorated = DurationAssertDecorator(1s, [](const double temperature) { return temperature + 5.0; });
double a = decorated (4);
构图也应该没问题:
std::function<double()> wrapped = LogDecorator(logger, [] { return 4.0; });
std::function<double()> wrapped_wrapped = DurationAssertDecorator(1s, functor);
这应该不行 - int literal 5 不是可调用的:
std::function<void(double)> decorated = DurationAssertDecorator(1s, 5);
到目前为止,它确实可以解决问题:
- 这种情况——被包装的函数有一个返回值——我不确定我是否只是通过自动获得结果,而被包装的返回值是一个引用。如果是这样,则将进行复制而不是保留引用(通过指针和值返回应该是可以的)。所以这就是为什么我想出了这个奇怪的结构。我可以做得更好吗?
- 还有哪些其他改进/修复是可能的?
解决方案
我已经意识到,如果我将 RAII 对象用于调用前和调用后的活动,我可以进一步简化代码。不再需要处理 void 和非 void 返回值。
template<typename TWrapped>
auto DurationAssertDecorator(const std::chrono::high_resolution_clock::duration& maxDuration, TWrapped&& wrapped)
{
return [wrapped = std::forward<TWrapped>(wrapped), maxDuration](auto&&... args) mutable
{
static_assert(std::is_invocable<TWrapped, decltype(args)...>::value, "Wrapped object must be invocable");
struct Aspect
{
// Precall logic goes into the constructor
Aspect(const std::chrono::high_resolution_clock::duration& maxDuration)
: _startTimePoint(std::chrono::high_resolution_clock::now())
, _maxDuration(maxDuration)
{}
// Postcall logic goes into the destructor
~Aspect()
{
const auto endTimePoint = std::chrono::high_resolution_clock::now();
const auto callDuration = endTimePoint - _startTimePoint;
assert(callDuration <= _maxDuration);
}
const std::chrono::high_resolution_clock::time_point _startTimePoint;
const std::chrono::high_resolution_clock::duration& _maxDuration;
} aspect(maxDuration);
return wrapped(std::forward<decltype(args)>(args)...);
};
}
它适用于正常的用例:
auto wrappedFunctor = DurationAssertDecorator(1s, [](const double temperature) { return temperature; });
我还想使用非常量函子来处理它,比如可变 lambda:
auto wrappedFunctor = DurationAssertDecorator(1s,
[firstCall = true](const double temperature) mutable
{
if (firstCall)
{
firstCall = false;
return temperature;
}
std::this_thread::sleep_for(2s);
return temperature;
});
所以我对这个解决方案很满意。
推荐阅读
- android - 如何仅检索firebase数据库中的某些节点
- java - GWT 复合小部件 - 按钮点击
- android - 使用 runOnUiThread 时内存泄漏
- laravel - 我需要对 Laravel 或 Pusher 上发送的每条消息进行排队吗?
- iframe - iframe 中的 Zoho Creator 添加了空行 - 我们如何停止这种行为
- java - 在 IntelliJ IDEA 的 App 类之外获取用户输入
- javascript - 访问同一目录中的另一个页面会产生带有文件的 CORS 错误:URL
- swift - Eureka:如何通过 onchange 函数更改隐藏或不隐藏的行
- http - 在颤振/飞镖中使用“接受:文本/事件流”作为标头发出 http 请求
- wxpython - wxPython - 从子进程更改状态栏文本