【问题标题】:Python Style Decorator in C++17C++17 中的 Python 风格装饰器
【发布时间】:2019-02-14 07:03:35
【问题描述】:

我正在使用最新的可用 C++ 技术制作类似 Python 的装饰器。我已经在这里看到了一些解决方案(Python-like C++ decorators),但我想知道它是否可以做得更好。在其他人 (Constructing std::function argument from lambda) 的帮助下,我想出了以下解决方案。

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

到目前为止,它确实可以解决问题:

  • 这种情况——被包装的函数有一个返回值——我不确定我是否只是通过自动获得结果并且被包装的返回值是一个引用。如果是这样,则将进行复制而不是保留引用(通过指针和值返回应该是可以的)。所以这就是为什么我想出了这个奇怪的结构。我可以做得更好吗?
  • 还有哪些可能的改进/修复?

【问题讨论】:

  • 您确定要在 lambda 捕获中从 wrapped 移动吗?似乎std::forward 可能是更好的选择。
  • 谢谢,我认为你是对的:forward 应该适用于正常和移动引用。我已经修改了问题中的代码

标签: c++ lambda stl c++17


【解决方案1】:

我已经意识到,如果我将 RAII 对象用于调用前和调用后的活动,我可以进一步简化代码。不再需要处理 void 和 non-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;
    });

所以我对这个解决方案很满意。

【讨论】:

    猜你喜欢
    • 2020-11-03
    • 2011-12-29
    • 2015-08-21
    • 1970-01-01
    • 1970-01-01
    • 2016-11-09
    • 1970-01-01
    • 2012-01-20
    • 1970-01-01
    相关资源
    最近更新 更多