0
点赞
收藏
分享

微信扫一扫

C++ AOP 编程介绍

书坊尚 2023-12-16 阅读 54

AOP(Aspect-Oriented Programming) 是一种编程范式,将程序的非核心逻辑都“横切”处理,实现非核心逻辑与核心逻辑的分离【1】

在日常工作中,会遇到一类需求:统计业务处理的耗时或者加锁,业务函数可以动态替换而非侵入式修改业务函数;

C++ AOP 编程介绍_AOP

简单粗暴的方法是:

Ret Process(...) // 业务函数
    {
        return {}; 
    }
    {   
        timeBegin = ...;
        Process(...);
        elapse = timeEnd = timeBegin;
    }

显然这是临时性的处理,重复代码与扩展等都会存在问题。

易扩展代码的关键在于分析业务逻辑的变化点,抽离变化而稳定公共的部分,这里的变化点包括:

  1. Process函数的变化,后续需要考虑替换其他Process函数
  2. elapse非核心业务的变化,后续可能是其他的统计处理,例如trace等

在【1】中介绍了应用代理模式进行设计,文中使用了继承方式,当然也可以使用组合方式,例如:

int Process(int)
{
    usleep(10000);
    return 0;
}

template<typename F>
struct TimeProxy {
    TimeProxy(F f) : m_f(f) {} // 隐式推导指南自动推导F类型
    template<typename ...Args>
    typename FunctionTraits<F>::RetType Process(Args&&...args)
    {
        std::chrono::time_point<std::chrono::high_resolution_clock> begin = std::chrono::high_resolution_clock::now();

        decltype(auto) ret = m_f(std::forward<Args>(args)...);

        int64_t elapseMs = std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - begin).count();
        DBG_LOG("elapse time: %lld", elapseMs);
        return ret;
    }
private:
    F m_f;
};

TimeProxy tp(Process);
tp.Process(0);

这里分离出了第一个变化点,将Process函数作为泛型传入,后续替换为其他类型函数时则不用修改TimeProxy的框架。

对于第二个变化点,同理进行抽离:

struct AspectTimer {
    AspectTimer() : m_begin(std::chrono::high_resolution_clock::now())
    {
        DBG_LOG("begin");
    }
    ~AspectTimer()
    {
        DBG_LOG("end elapse time: %lld",
            std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_begin).count());
    }
private:
    std::chrono::time_point<std::chrono::high_resolution_clock> m_begin;
};

template<typename Aspect>
struct AspectProxy {
    template<typename F, typename ...Args>
    static decltype(auto) Process(F f, Args&&...args)
    {
        Aspect aop {};
        return f(std::forward<Args>(args)...);
    }
};

AspectProxy<AspectTimer>::Process(Process, 0);

AspectTimer为一个RAII对象,由调用者动态织入,实现了两个变化点的扩展。

此时函数是全局或者静态函数,进一步支持类成员函数,例如在DFX维测时对某类或其基类函数的切面织入,修改为:

template<typename Aspect>
struct AspectProxy {
    template<typename F, typename ...Args>
    static decltype(auto) Process(F f, Args&&...args)
    {   
        Aspect aop {}; 
        return f(std::forward<Args>(args)...);
    }   
};

#define INVOKE(ASPECT, CALLER, FUNC, ...) \
    AspectProxy<ASPECT>::Process([&CALLER](auto&& ...args) \
        { \
            return CALLER.FUNC(std::forward<decltype(args)>(args)...); \
        }, __VA_ARGS__) \
        
Test t{};
INVOKE(AspectTimer, t, UsrProcess, 0);  // 统计派生类调用
INVOKE(AspectTimer, t, Base::UsrProcess, 0);  // 统计基类调用

这时需要多个切面的组合,扩展不同的切面函数:

C++ AOP 编程介绍_AOP_02

完整代码如下:

class Base {
public:
    virtual int UsrProcess(int)
    {
        usleep(10000);
        DBG_LOG();
        return 0;
    }
};

class Test : public Base {
public:
    int UsrProcess(int) override
    {
        usleep(10000);
        DBG_LOG();
        return 0;
    }
};

struct AspectTimer {
    AspectTimer() : m_begin(std::chrono::high_resolution_clock::now())
    {
        DBG_LOG("begin");
    }
    ~AspectTimer()
    {
        DBG_LOG("end elapse time: %lld",
            std::chrono::duration_cast<std::chrono::milliseconds>(std::chrono::high_resolution_clock::now() - m_begin).count());
    }
private:
    std::chrono::time_point<std::chrono::high_resolution_clock> m_begin;
};

struct Aspect1 {
    Aspect1()
    {
        DBG_LOG("begin");
    }
    ~Aspect1()
    {
        DBG_LOG("end");
    }
};

struct Aspect2 {
    Aspect2()
    {
        DBG_LOG("begin");
    }
    ~Aspect2()
    {
        DBG_LOG("end");
    }
};

template<typename ...Types>
class AspectTypes {
public:
    template<std::size_t I>
    struct GetType {
        using Type = typename std::tuple_element<I, std::tuple<Types...>>::type;
    };
};

template<typename Type>
struct AspectProxy;

template<typename ...Aspects>
struct AspectProxy<AspectTypes<Aspects...>> {
    template<typename F, typename ...Args>
    static decltype(auto) Process(F&& f, Args&&...args)
    {
        return Invoke(std::make_index_sequence<sizeof...(Aspects)> {}, std::forward<F>(f), std::forward<Args>(args)...);
    }

    template<std::size_t I, typename F, typename ...Args>
    static decltype(auto) Invoke(std::index_sequence<I>, F&& f, Args&&... args)
    {
        using AspectFuncType = typename AspectTypes<Aspects...>::template GetType<I>::Type;
        AspectFuncType asp {};
        return std::forward<F>(f)(std::forward<Args>(args)...);
    }

    template<std::size_t I, std::size_t... R, typename F, typename ...Args>
    static decltype(auto) Invoke(std::index_sequence<I, R...>, F&& f, Args&&... args)
    {
        using AspectFuncType = typename AspectTypes<Aspects...>::template GetType<I>::Type;
        AspectFuncType asp {};
        return Invoke(std::index_sequence<R...> {}, std::forward<F>(f), std::forward<Args>(args)...);
    }
};

#define AOP(...) AspectTypes<__VA_ARGS__>

#define INVOKE(ASPECTS, CALLER, FUNC, ...) \
    AspectProxy<ASPECTS>::Process([&CALLER](auto&& ...args) \
        { \
            return CALLER.FUNC(std::forward<decltype(args)>(args)...); \
        }, __VA_ARGS__) \
        
Test t{};
INVOKE(AOP(AspectTimer, Aspect2, Aspect1), t, Base::UsrProcess, 0);
INVOKE(AOP(AspectTimer, Aspect1, Aspect2), t, UsrProcess, 0);

但是要注意:随着切面的增多,调用栈增加性能也会有所损失,需要结合实际业务场景进行应用。

参考资料

【1】深入应用C++11代码优化与工程级应用

【2】http://vitiy.info/c11-functional-decomposition-easy-way-to-do-aop/

举报

相关推荐

C#---AOP面向切面编程应用

C++ list 介绍

C/C++网络编程

C++谓词介绍

C++ 指针介绍

C++ EBO介绍

C++ 提高编程

0 条评论