【CPP】【函数装饰器宏】cpp11利用宏、模板以及lambda表达式实现python like的装饰器语义
前言
python函数的装饰器是一个轻量级的函数功能拓展和复用工具,相比传统的派生重写虚函数的方式来说,避免了继承带来的耦合负担,控制也相对更加的简单,但cpp并没有相关的原生关键字支持,我参考了一些知乎和CSDN上的文章,但始终没有达到我想要的效果于是准备自己也动动手。
cpp11的lambda表达式提供了最基本的特性,基于此特性,我实现了类似语义的宏,跟python的函数装饰器相比创建形式并不相同,但是功能是相似的。
我事先在CSDN上发了一版,这一版是更新优化后的版本,装饰语法更加简洁,并且提供了cpp11兼容void返回值的解决方案。
用法
style1
int CCar::oilfeed(int degress)
DECORATE(logging,,
DECORATE(novoid,ARGS(),//对于装饰器的使用来说,如果没有参数,那么需要一个ARGS()占位或留空,留空看上去就是两个连续的','
DECORATE(maybe_void,,
//使用UNDECORATE宏可以单点禁用一个装饰器,原理是去掉了该层的 [&](){}包装器并悬空checkRotateSpeed以及ARGS里面的东西只保留代码块
UNDECORATE(checkRotateSpeed, ARGS(this, __FUNCTION__), //传递多个参数
{
m_n_rotat_speed += degress;
return m_n_rotat_speed;
})))) //用了多少个装饰器后面就要多少个),没办法cpp只能这样了,想不出更好的办法,本质是宏嵌套
style2
void CCar::brake(int degress)
DECORATE(logging, ARGS(__FUNCTION__),
//DECORATE(novoid,ARGS(), 使用novoid装饰器无法直接通过编译,因为不支持 void 作为返回值的偏特化
DECORATE(maybe_void,,
//推荐用仿函数的方式实现装饰器,通过构造函数传参封装性更好,如果舍弃函数类型的装饰器的话,就可以少一个宏参数占位了不用打奇怪的两个逗号了,
DECORATE(CheckRotateSpeed(this, __FUNCTION__),,
{
m_n_rotat_speed -= degress;
})))
msvc2015 x64执行结果
完整测试代码
decorator.h
#ifndef DECORATOR_H
#define DECORATOR_H
#include <functional>
template<bool T>
//!
//! \brief The bool_trait struct
//! \usecase bool_trait<静态表达式->bool> 用于模拟模板偏特化
//!
struct bool_trait{};
template <typename T>
class func_caller final
{
public:
using return_type = typename std::result_of<T&()>::type;
using void_trait = bool_trait<std::is_same_v<void, return_type>>;
explicit func_caller(T const &func)
: stack(nullptr)
{
init(void_trait());
call(func, void_trait());
}
~func_caller()
{
release(void_trait());
}
return_type ret() const
{
return ret(void_trait());
}
private:
void* operator new(std::size_t) = delete;
func_caller(func_caller const &) = delete;
func_caller& operator=(func_caller const &) = delete;
void init(bool_trait<true> const&)
{
}
void init(bool_trait<false> const&)
{
stack = new return_type;
}
void release(bool_trait<true> const&)
{
}
void release(bool_trait<false> const&)
{
delete stack;
}
void call(T &func, bool_trait<true> const&)
{
func();
}
void call(T const &func, bool_trait<false> const&)
{
*stack = std::move(func());//尽量调用移动构造函数
}
void ret(bool_trait<true> const&) const
{
}
return_type ret(bool_trait<false> const&) const
{
return *stack;
}
return_type* stack;
};
//打包参数的宏,仿函数装饰器可以不使用
#define ARGS(...)\
,__VA_ARGS__
//启用装饰器语义宏定义
#ifndef NO_DECORATOR
//装饰宏,其实就是用 [&](){}把代码块包起来
#define DECORATE(decorator, args, codeblock) \
{\
return decorator( [&]() codeblock args);\
}
//单独禁用某个装饰器
#define UNDECORATE(decorator, args, codeblock) \
codeblock
#else
//禁用所有装饰器
#define DECORATE(decorator, args, codeblock) \
codeblock
#define UNDECORATE(decorator, args, codeblock) \
codeblock
#endif // NO_DECORATOR
//隐藏模板语义,定义一个装饰器模板函数
#define DEF_DECIRATOR(name, .../*args*/) \
template <typename T>\
auto name(T const &func, __VA_ARGS__)
//实现临时快速禁用一个装饰器模板函数的功能,修改原装饰器为直接回调,然后将原本的逻辑添加到一个起始参数为void*的同名函数里面
#define DIS_DECIRATOR(name, .../*args*/) \
template <typename T>\
auto name(T const &func, __VA_ARGS__) \
{ return func(); }\
template <typename T>\
auto name(void *, T const &func, __VA_ARGS__)
//简化func_caller的使用方法
#define CALL() \
func_caller<decltype(func)>(func)
#endif // DECORATOR_H
rm_decorator.h
#ifndef RM_DECORATOR_H
#define RM_DECORATOR_H
//禁用所有装饰器相关宏
#undef ARGS
#undef DECORATE
#undef UNDECORATE
#undef CALL
#undef DEF_DECIRATOR
#undef DIS_DECIRATOR
#endif // RM_DECORATOR_H
main.cpp
#include <functional>
#include <iostream>
#include <chrono>
#include <string>
//#define NO_DECORATOR
#include "base/decorator.h"
class CCar{
public:
explicit CCar(){
}
int oilfeed(int degress);
void brake(int degress);
int rotat_speed() const {return m_n_rotat_speed;}
private:
int m_n_rotat_speed = 2000;
};
/**
* 注:此装饰器原理实为创建引用捕获的lambda表达式并调用,所以性能肯定会有所下降,但是增加了可读性,减少了维护难度
**/
//用原汁原味的写法定义装饰器函数,但是不能用过宏快速禁用,因为快速禁用实际是用重载实现的,所以只能增加代码分支进行控制如下面的写法
template <typename T>
auto checkRotateSpeed(T const &func/*第一个参数必须为函数*/, CCar const *obj, std::string const &funcname)
{
#if 1
std::cout << "checkRotateSpeed before " << funcname << ", rotat_speed:" << obj->rotat_speed() << std::endl;
auto const &caller = func_caller<decltype(func)>(func);//用func_caller模板来处理返回值为void的情况,缺点是非void返回值会多一些内存拷贝,所以条件允许的话对于大对象尽量使用智能指针作为返回值
std::cout << "checkRotateSpeed after " << funcname << ", rotat_speed:" << obj->rotat_speed() << std::endl;
return caller.ret();
#else
return func();
#endif
}
//如果不用考虑返回值为 void,装饰器函数直接这么写就可以了,即简单性能又好
template <typename T>
auto novoid(T const &func)
{
std::cout << "novoid before " << std::endl;
auto const &ret = func();//直接调用被包装的函数获取结果,不用再返回一个包装器了是多此一举,包装器我是用装饰宏 DECORATE 封装了 [&]() 语句实现的,从这点看比python要简洁
std::cout << "novoid after " << std::endl;
return ret;
}
DEF_DECIRATOR //用宏语义快速定义装饰器函数
//DIS_DECIRATOR //快速禁用装饰器函数,原理为自动替换实现为 return func();
(logging, std::string const &funcname = "unknow func", int others = 0)
{
(void) others;
//在调用被装饰函数之前的操作
std::cout <<"log before " << funcname << std::endl;
auto start = std::chrono::high_resolution_clock::now();
auto const &caller = CALL();//使用CALL宏精简代码 func_caller<decltype(func)>(func)
auto const &end = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> time_span = std::chrono::duration_cast<std::chrono::duration<double>>(end - start);
std::cout << "log after " << funcname << ": elapsed time(s): " << time_span.count() << std::endl;
return caller.ret();
}
//兼容 void 返回值,不用 func_caller 的高性能的写法,考虑兼容c++11的话缺点是要把实现拷贝一份,有代码冗余不好维护,而且比较复杂要用一些奇技淫巧
//如果您的环境是c++17就不用看这个 maybe_void 了,直接用if constexpr吧
template<typename T>
auto maybe_void(T const &func)
{
using ReturnType = typename std::result_of<T&()>::type;
using IsVoidReturn = bool_trait<std::is_same_v<void, ReturnType>>;
return maybe_void(func, IsVoidReturn());
}
using ReturnIsVoid = bool_trait<true>;
using ReturnIsntVoid = bool_trait<false>;
// 对于非void返回值的重载
template<typename T>
auto maybe_void(T const &func, ReturnIsntVoid const &)
{
std::cout << "maybe_void before " << std::endl;
auto const &ret = func();
std::cout << "maybe_void after " << std::endl;
return ret;
}
// 对于void返回值的重载
template<typename T>
void maybe_void(T const &func, ReturnIsVoid const &)
{
std::cout << "maybe_void before " << std::endl;
func();
std::cout << "maybe_void after " << std::endl;
}
//定义一个仿函数装饰器,无论是函数装饰器还是仿函数装饰器,第一个参数都得是std::function<T ()> 类型否则装饰器宏无法正常展开
class CheckRotateSpeed
{
public:
CheckRotateSpeed(CCar const *obj, std::string const &funcname)
:m_p_obj(obj)
,m_str_funcname(funcname)
{
}
//仿函数同样适用快速定义或禁用宏,模板的风格请参考 novoid 和 novoid_
DEF_DECIRATOR
//DIS_DECIRATOR
(operator ())
{
//在调用被装饰函数之前的操作
std::cout << "checkRotateSpeed before " << m_str_funcname << ", rotat_speed:"<< m_p_obj->rotat_speed() << std::endl;
auto const &caller = CALL();
//在调用被装饰函数之前的操作
std::cout << "checkRotateSpeed after " << m_str_funcname << ", rotat_speed:"<< m_p_obj->rotat_speed() << std::endl;
return caller.ret();
}
private:
CCar const *m_p_obj;
std::string m_str_funcname;
};
int CCar::oilfeed(int degress)
DECORATE(logging,,
DECORATE(novoid,ARGS(),//对于装饰器的使用来说,如果没有参数,那么需要一个ARGS()占位或留空,留空看上去就是两个连续的','
DECORATE(maybe_void,,
//使用UNDECORATE宏可以单点禁用一个装饰器,原理是去掉了该层的 [&](){}包装器并悬空checkRotateSpeed以及ARGS里面的东西只保留代码块
UNDECORATE(checkRotateSpeed, ARGS(this, __FUNCTION__), //传递多个参数
{
m_n_rotat_speed += degress;
return m_n_rotat_speed;
})))) //用了多少个装饰器后面就要多少个),没办法cpp只能这样了,想不出更好的办法,本质是宏嵌套
void CCar::brake(int degress)
DECORATE(logging, ARGS(__FUNCTION__),
//DECORATE(novoid,ARGS(), 使用novoid装饰器无法直接通过编译,因为不支持 void 作为返回值的偏特化
DECORATE(maybe_void,,
//推荐用仿函数的方式实现装饰器,通过构造函数传参封装性更好,如果舍弃函数类型的装饰器的话,就可以少一个宏参数占位了不用打奇怪的两个逗号了,
DECORATE(CheckRotateSpeed(this, __FUNCTION__),,
{
m_n_rotat_speed -= degress;
})))
//移除decorator相关宏,如果后文遇到重名的宏的话
#include "base/rm_decorator.h"
int main(int , char *[])
{
CCar car;
car.oilfeed(45);
car.brake(1000);
return 0;
}