首页 > 编程语言 >C++ AOP 编程介绍

C++ AOP 编程介绍

时间:2023-12-16 20:31:49浏览次数:42  
标签:std ... args return Process 编程 C++ template AOP

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 编程介绍_CPP_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/

标签:std,...,args,return,Process,编程,C++,template,AOP
From: https://blog.51cto.com/u_13137973/8854076

相关文章

  • c++单词排序。
    --START--读入n个英文单词,将这n个单词按字典序升序排序后,重新输出。第1行,一个正整数n。(0<n<100)第2行,n个英文单词。单词之间用空格隔开。一行,n个按字典序升序排序后的英文单词。单词之间用空格隔开。in:5hiIamastudentout:Iamahistudent#include<iostream>......
  • cuda编程的简单案例
    一个简单的案例:header.hvoidaddKernel(constint*a,constint*b,int*c,intsize); test.cu#include"cuda_runtime.h"#include"device_launch_parameters.h"#include"header.h"__global__voidadd(constint*a,constint*......
  • C++U5-10-二叉树3
    学习目标 二叉树重建的概念 二叉树重建流程 例题和解题思路 2 3 4 5 [【二叉树】求先序排列]  代码【算法分析】后序遍历的最后一个是根节点,由这个根节点可以在中序遍历中确定左子树和右子树的大小和元素,然后递归的去处理左子树和右子树,由于是......
  • AOP
    2023-12-1616:42:561.AOP1.1作用保证开发者不修改源代码的前提下,去为系统中的业务组件添加某种通用功能。1.2注解声明切入点  ①   使用execution指示器选择方法,方法表达式以*号开始,标识我们不关心方法的返回值类型。然后我们指定了全限定类名和方法名。对于方法......
  • C++ Qt开发:Tab与Tree组件实现分页菜单
    Qt是一个跨平台C++图形界面开发库,利用Qt可以快速开发跨平台窗体应用程序,在Qt中我们可以通过拖拽的方式将不同组件放到指定的位置,实现图形化开发极大的方便了开发效率,本章将重点介绍tabWidget选择夹组件与TreeWidget树形选择组件,的常用方法及灵活运用。1.1TabWidgetQTabWidget......
  • 实验7_文件应用编程
    task4#define_CRT_SECURE_NO_WARNINGS#include<stdio.h>#include<stdlib.h>intmain(){FILE*fp;charch;intcnt=0;fp=fopen("data4.txt","r");if(fp==NULL){printf("failtoop......
  • C++: 智能指针的自定义删除器 `Custom Deleter` 有什么用?
    C++11智能指针std::shared_ptr和std::unique_ptr都支持自定义删除器,本文将介绍自定义删除器的使用场景和使用方法。智能指针模板参数的第二个类型是删除器,一般是一个函数指针类型或者是一个函数对象类型。通常情况下,删除器的类型是std::default_delete<T>,它是一个函数对象类型,用于......
  • C++ 高效使用智能指针的8个建议
    C++高效使用智能指针的8个建议前言:智能指针是C++11提供的新特性,它基于RAII实现,可以自动管理内存资源,避免内存泄漏的发生,但是智能指针也并不是万能的,如果不正确使用智能指针,也会导致内存泄漏的发生,因此,我们需要了解如何高效使用智能指针避免一些可能的陷阱。本文总结了8个关于智......
  • 【教3妹学编程-算法题】反转二叉树的奇数层
    3妹:“你不是真正的快乐,你的笑只是你穿的保护色”2哥 :3妹还在唱五月天的歌啊,你不知道五月天假唱,现在全网都在骂呢。3妹:知道啊,可是关我什么事,这个歌的确好听啊。2哥 :嗯嗯,不错,还以为你是脑残粉,无论黑白都只管追星呢。3妹:我是只管追歌的,歌好听就行啦。2哥 :追哥?追哪个哥,难......
  • 商用软件调用开源代码后硬分叉不合并 —— 一种合法的防御性编程或者是商用软件的贪婪
    看到一个说法,说前段时间某滴的公司代码升级导致错误最后使全公司业务崩溃一整天的事情是因为公司商用软件中使用了一种合法的防御性编程,也就是商用软件调用开源代码后硬分叉不合并。 可以说商业企业大幅度使用开源软件已经是公开的秘密了,但是出于实际情况这些不合规的将开源软......