id | title | desc | updated | created |
---|---|---|---|---|
vzez3ilwus6l6lgyirixyvo |
Template_param_package |
1700631007896 |
1700532167019 |
一个自定义的单例日志模块,此前的配置都是在单例的某个虚函数中实现的,此时配置属于模块,而模块被所有目标target共同引用,那么将会面临配置的冲突和重合
- 方案1: 每一个target进行日志引用时,继承基础单例,实现自己的日志模块,即生成新的对象(新模块)进行引用(本质上是一种静态的配置),存在问题,同一个target下的所有引用可以统一为新的日志对象,但target外的公告模块进行日志引用,将引用的日志基础类,引用存在冲突
- 方案2: 为了统一外部公共模块以及target内部模块日志的引用,首先确定了继承之路行不通,还是要统一一个公共引用,即同一个单例,并且同一个接口,在运行时通过某个接口进行动态配置或者在运行时传入回调,在第一次单例构造时callback进行自动配置。
- 配置时,需要进行不定个sink的生成以及注册,还有个别固定参数的传参
- 若在外部生成时则需要个别protect函数
- 重定义个别 gen 函数由外部调用
- 定义工厂函数由外部传参构造参数,实现内部的注册与构造
- 递归
- 逗号展开
void print() {
printf("no args print\n");
}
void print(int arg) {
printf("base print :%d\n", arg);
}
//recursive expand
template<typename Arg1, typename Arg2, typename ...Args>
void print(int fixed, Arg1 arg1, Arg2 arg2, Args... args) {
print(fixed, args...);
}
//1 2 3 4 5
//1 3 5
//1
//1 2 3 4
//1 4
//无法展开,不满足三个参数
//1 2 3
//1
//1
//无法展开,不满足三个参数
//规律: 参数>2, 并且为计数,因为每次展开消费两个arg, 递归到最后需要存在一个参数, 即必须是大于1的奇数
//comma expand
template<typename T>
void printarg(T t) {
std::cout << t << std::endl;
}
template<typename ...Args>
void expand(Args... args) {
int arr[] = {(printarg(args), 0)...};
}
int main(int argc, char** argv) {
//template expand
expand(1, 2, 3, 4);
print(1, 2, 3, 4, 5);
print(1, 2, 3);
print(1);
return 0;
}
模板的目标: 通过编译期 模板引用时
的传参
来指导编译器在模板定义处
进行代码生成
递归生成一个完整的函数实现, 进而 在运行时直接引用,本质上是通过代码生成的语法强化接口功能
所以模板不定参的递归展开的关键:
- ...Args 在传递过程可以为0个参数,每次递归用当前形参不定参...Args重新作为实参填入,并占位原先的固定位置参数,从而实现...Args 一点点的缩短被提取到固定位置,直到匹配到特定函数递归结束
- 固定位置参数 是明确的,用来进行引用或者传递
- 关键在于 设计模板函数参数的固定位置参数和递归结束函数
不定参模板函数,逗号展开的关键:
- 可变参数模板的展开方法:
- 最简单,可变参形参后加...
args... //展开的结果就是逗号分隔的一列实际参数
- 与args关联的表达式展开逻辑,在包含args的表达式中,会对实际的每一个参数进行表达式中的运算并使用逗号分隔,表达式中的args可以看作是实际参数的占位符
auto tmp = { (args * args)...}; auto tmp1 = { std::sin(args)...}; auto tmp2 = { (std::sin(args) + args)...}; //expand to auto tmp = {(1 * 1),(2 * 2),(3 * 3)}; auto tmp1 = { std::sin(1), std::sin(2), std::sin(3) }; auto tmp2 = { (std::sin(1) + 1), (std::sin(2) + 2), (std::sin(2) + 2) };
- 逗号表达式:
- t=(print(), c) 逗号表达式可以实现先执行逗号前表达式,然后返回逗号后表达式,赋值给t
- 连续多个逗号呢,auto a = (funA(), funB(), funC()); 所有表达式依次执行,最后只返回最后一个表达式的返回值
- 初始化列表:
- 元素必须是同类型
- 必须是确定类型,不能为void
- 为什么展开时使用初始化列表?
- std::initializer_list 与 {}: {}是语法表达式生成initializer_list类型的对象,而initializer_list通常表示形参时使用,来接受{}表达式的结果以及传递。
首先,可变参数包需要展开,展开即需要入口(entry), 一个选择是作为实参传入函数接口中,另一个选择即是c++语法糖,初始化列表,通过一个{}组成的表达式,填入实参列表来直接初始化一个对象,故可理解为初始化列表在此作为参数包展开的容器,仅仅为了容纳而已 紧接上面的问题,第二个: 为什么是逗号表达式,答案在初始化列表本身的性质中,由于初始化列表要求元素同类型,那么对于参数包而言,所有实参展开后的类型都有可能不同,如何实现在初始化列表中展开 类型不同的实参?通过逗号表达式的技巧,使得即能执行每一个展开表达式,又能返回一个统一的类型或值
函数的递归: 在于编译期 编写判断条件,递归重复引用,在运行时 进行条件判断,最终跳出引用