如何给C++装饰器传递任意参数
在前面的文章中,实现了C++版本的装饰器的功能,但是有一些不是很完美。例如,对于lambda表达式和普通函数的封装,一个需要引用一个不需要引用,不是很统一。
第二,在给C++装饰器传递任意参数上,我们采用了C++20标准。但是目前的编译器,对于C++20的标准的支持并不是很完全,需要对编译器进行升级,不是很方便,因此本文主要是基于C++17标准实现Python装饰器所有功能。
代码示例
#include <iostream>
#include <functional>
#include <string>
#include <tuple>
template <typename Func, typename... DecoratorArgs>
class Decorator {
public:
Decorator(Func&& func, DecoratorArgs&&... args)
: func_(std::forward<Func>(func)), args_(std::forward<DecoratorArgs>(args)...) {}
template <typename... CallArgs>
auto operator()(CallArgs&&... callArgs) -> decltype(auto) {
printDebugInfoIfRequested();
std::cout << "Before function call" << std::endl;
// 使用 if constexpr 来区分处理有返回值和无返回值的函数
if constexpr (std::is_void_v<decltype(std::invoke(std::declval<Func>(), std::forward<CallArgs>(callArgs)...))>) {
// 如果函数返回 void,直接调用
std::invoke(std::forward<Func>(func_), std::forward<CallArgs>(callArgs)...);
} else {
// 如果函数有返回值,调用并返回结果
return std::invoke(std::forward<Func>(func_), std::forward<CallArgs>(callArgs)...);
}
std::cout << "After function call" << std::endl;
printDebugInfoIfRequested();
}
private:
Func func_;
std::tuple<DecoratorArgs...> args_;
private:
void printDebugInfoIfRequested() {
// 使用 std::apply 和一个 lambda 表达式来检查参数包中的每个元素
bool debugRequested = false;
std::apply([&debugRequested](auto&&... args) {
bool localDebug = ((std::is_same_v<std::decay_t<decltype(args)>, const char*> && args == std::string("debug")) || ...);
debugRequested = debugRequested || localDebug;
}, args_);
if (debugRequested) {
std::cout << "Debug mode: Logging " << std::endl;
}
}
};
template <typename Func, typename... DecoratorArgs>
Decorator<Func, DecoratorArgs...> create_decorator(Func&& func, DecoratorArgs&&... args) {
return Decorator<Func, DecoratorArgs...>(std::forward<Func>(func), std::forward<DecoratorArgs>(args)...);
}
int add(int a, int b) {
std::cout << "Executing add" << std::endl;
return a + b;
}
void greet() {
std::cout << "Hello, World!" << std::endl;
}
int main() {
auto decoratedAddWithDebug = create_decorator(add, "debug");
std::cout << "Result of add: \n" << decoratedAddWithDebug(5, 6) << std::endl;
auto decoratedGreet = create_decorator(greet, "debug");
decoratedGreet ();
auto addOne = [](int x) { return x + 1; };
auto decoratedAddOne = create_decorator(addOne, "debug");
std::cout << "Result of add: \n" << decoratedAddOne(5) << std::endl;
return 0;
}
主要思路
上面的代码实现了一个通用的装饰器类模板 Decorator
,它可以接受任意类型的可调用对象(如函数、函数对象、lambda 表达式等)以及任意数量的装饰器参数。以下是实现这个装饰器类的具体思路:
1. 类模板定义
Decorator
类模板定义为:
template <typename Func, typename... DecoratorArgs>
class Decorator {
// 类成员定义
};
这里,Func
是被装饰的可调用对象的类型,而 DecoratorArgs
是装饰器参数的类型,它们可以是任意类型,因为使用了参数包。
2. 构造函数
Decorator
类包含一个构造函数,它接受一个可调用对象和任意数量的装饰器参数,并将它们存储在成员变量中:
Decorator(Func&& func, DecoratorArgs&&... args)
: func_(std::forward<Func>(func)), args_(std::forward<DecoratorArgs>(args)...) {}
这里使用了 std::forward
来完美转发参数,确保参数的左值/右值性质得以保持。
3. 装饰逻辑
Decorator
类重载了 operator()
,允许它像函数一样被调用。这个操作符接受任意数量的调用参数,并根据被装饰函数的返回类型进行处理:
template <typename... CallArgs>
auto operator()(CallArgs&&... callArgs) -> decltype(auto) {
printDebugInfoIfRequested();
std::cout << "Before function call" << std::endl;
if constexpr (std::is_void_v<decltype(std::invoke(std::declval<Func>(), std::forward<CallArgs>(callArgs)...))>) {
std::invoke(std::forward<Func>(func_), std::forward<CallArgs>(callArgs)...);
} else {
return std::invoke(std::forward<Func>(func_), std::forward<CallArgs>(callArgs)...);
}
std::cout << "After function call" << std::endl;
printDebugInfoIfRequested();
}
这里使用了 if constexpr
来区分被装饰函数是否返回 void
。如果是 void
,则直接调用;否则,返回调用结果。
4. 调试信息打印
Decorator
类包含一个私有成员函数 printDebugInfoIfRequested
,用于检查装饰器参数中是否包含调试信息,并在需要时打印:
void printDebugInfoIfRequested() {
bool debugRequested = false;
std::apply([&debugRequested](auto&&... args) {
bool localDebug = ((std::is_same_v<std::decay_t<decltype(args)>, const char*> && args == std::string("debug")) || ...);
debugRequested = debugRequested || localDebug;
}, args_);
if (debugRequested) {
std::cout << "Debug mode: Logging " << std::endl;
}
}
这里使用了 std::apply
和参数包展开来检查是否有参数等于字符串 "debug"
。
附注:这里的调试信息打印打印功能,就是根据装饰器参数进行处理的行为,可以根据对装饰器传参的不同而执行不同的函数。
5. 创建装饰器
提供了一个 create_decorator
函数模板,用于创建 Decorator
实例:
template <typename Func, typename... DecoratorArgs>
Decorator<Func, DecoratorArgs...> create_decorator(Func&& func, DecoratorArgs&&... args) {
return Decorator<Func, DecoratorArgs...>(std::forward<Func>(func), std::forward<DecoratorArgs>(args)...);
}
这个函数使用完美转发来创建 Decorator
对象,确保参数的左值/右值性质得以保持。
附注:如果觉得Decorator直接return似乎增加了Decorator副本构造的开销,那么也可以使用指针的方式来实现,不过这样的话,似乎在调用形式上与Python版本的装饰器调用不太一致了,这里我们并不为了追求极致的性能,而是追求函数调用的简洁性。
输出结果
运行输出结果如下所示:
Result of add:
Debug mode: Logging
Before function call
Executing add
11
Debug mode: Logging
Before function call
Hello, World!
After function call
Debug mode: Logging
Result of add:
Debug mode: Logging
Before function call
6
从中可以看出,能够很好的实现装饰器功能,并且具有任意类型与任意参数的通用性。
总结
这个 Decorator
类模板的设计允许它灵活地处理各种类型的可调用对象和装饰器参数,同时提供了一种简洁的方式来添加装饰逻辑,如调试信息打印。通过使用现代 C++ 特性,如模板参数包、std::forward
、if constexpr
和 std::apply
,这个装饰器类提供了一种强大而灵活的方式来扩展函数的行为。
本代码经过实测可以运行,如果有什么问题,欢迎留言。