学习参考书籍:王健伟《C++新经典:模板与泛型编程》
类模板中的友元
友元,即若A为B的友元,那么A可以访问B中的所有成员(任何修饰符修饰)。
友元类
1.类模板的实例成为友元类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29
| #include <iostream> using namespace std;
template<typename U> class B; template<typename T> class A { friend class B<long>; private: int data; };
template<typename U> class B { public: void callBAF() { A<int> atmpobj; atmpobj.data = 5; cout << atmpobj.data << endl; } };
int main() { B<long> bobj; bobj.callBAF(); }
|
在上面例子中,我们让类模板B
的实例B<long>
成为类模板A的友元类,因此可以在实例B<long>
中访问类模板A
的私有变量data
。需要注意的语法如下:
1 2
| template<typename U> class B; friend class B<long>;
|
2.类模板成为友元类模板
1 2 3 4 5 6 7 8 9
| template<typename T> class A { template<typename> friend class B; private: int data; };
|
3.类型模板参数成为友元类
让某个模板将其为类类型的模板参数作为其友元类。
1 2 3 4 5 6 7 8 9 10 11 12 13
| template<typename T> class A { friend T; }
class B { void callBAF() { A<B> aobj; } }
|
友元函数
1.函数模板的实例成为友元函数
若将实例化后的模板函数func<int,int>
作为普通类A
的友元函数,语法如下:
1 2
| friend void func<int,int>(int,int); template<typename U, typename V> void func(U val1, V val2);
|
2.函数模板成为友元函数模板
若我们想让函数模板的所有实例都成为类A的友元函数,那么我们直接将函数模板作为类A的友元模板。
1
| template<typename U, typename V> friend void func(U val1,V val2);
|
和类模板成为友元模板一样,在类A的的定义前毋需声明模板。
可变参模板
可变参模板运行模板定义中含有0到多个模板参数。
可变参函数模板
1 2 3 4 5 6 7 8 9 10
| template<typename... T> void myfunc(T... args) { cout << sizeof...(args) << endl; cout << sizeof...(T) << endl; } int main() { myfunc(25, "mstifiy", 0.7); }
|
sizeof...
用于表示收到模板参数个数和类型数量,固定语法,C++11引入。
为了将接收到的参数解包,使用递归调用实现。
方式一:参数包展开函数+同名递归终止函数
1 2 3 4 5 6 7 8 9 10 11
| void myfunc() { cout << "参数包解包递归函数终止" << endl; }
template<typename T, typename... U> void myfunc(T firstarg, U... otherargs) { cout << "收到的参数值为:" << firstarg << endl; myfunc(otherargs...); }
|
递归终止函数必须在递归展开函数前定义,否则报错。
方式二:constexpr if
C++17标准中引入了编译期间if语句,即在编译时只有满足条件代码才会被编译。重写myfunc函数模板如下:
1 2 3 4 5 6 7 8 9 10 11 12 13
| template<typename T, typename... U> void myfunc(T firstarg, U... otherargs) { cout << "收到的参数值为:" << firstarg << endl; if constexpr (sizeof...(otherargs) > 0) { myfunc(otherargs...); } else { cout << "参数包解包递归函数终止" << endl; } }
|
折叠表达式
折叠表达式的引入方便了需要所有可变参数参与计算才能得到的表达式结果的书写,即不用像上述一样将可变参数包解包再计算,而是可以直接通过简短的折叠表达式进行计算。折叠表达式一般有四种格式,每种格式都是用圆括号括起来的。参数从左侧开始计算叫左折,从右侧开始计算叫右折。
1.一元左折
格式:(... 运算符 一包参数)
计算方式:(((参数1 运算符 参数2) 运算符 参数3) ··· 运算符 参数N)
2.一元右折
格式:(一包参数 运算符 ...)
计算方式:(参数1 运算符 (··· (参数N-1 运算符 参数N)))
3.二元左折
格式:(init 运算符 ... 运算符 一包参数)
计算方式:(((init 运算符 参数1) 运算符 参数2) ··· 运算符 参数N)
4.二元右折
格式:(一包参数 运算符 ... 运算符 init)
计算方式:(参数1 运算符 (··· (参赛N 运算符 init)))
可变参表达式
折叠表达式主要体现的是参数之间的运算,当需要让可变参数本身进行一些运算时,可以使用可变参表达式。
格式:((可变参数自身运算) ...)
可变参类模板
模板参数列表语法和可变参函数模板一样,这里主要学习几种参数包的展开方式。
方式一:递归继承展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44
| #include <iostream> using namespace std;
template<typename... args> class myclasst { public: myclasst() { printf("myclasst::myclasst()泛化版本执行了,this=%p\n", this); } };
template<> class myclasst<> { public: myclasst() { printf("myclasst::myclasst()特殊的特化版本执行了,this=%p\n", this); } };
template<typename First, typename... Others> class myclasst<First, Others...> :private myclasst<Others...> { public: myclasst() :m_i(0) { printf("myclasst::myclasst()偏特化版本执行了,this = %p,sizeof...(Others)=%d\n", this, sizeof...(Others)); }
myclasst(First parf, Others... paro) :m_i(parf), myclasst<Others...>(paro...) { printf("myclasst::myclasst(parf, ...paro)执行了,this = %p\n", this); cout << "m_i = " << m_i << endl; } First m_i; };
int main() { myclasst<int, float, double> myc(12, 13.5f, 30.0); }
|
对于类模板可变参模板参数的展开操作,递归继承主要使用类模板的偏特化进行继承递归。对于非类型模板参数和模板模板参数,语法相似。
非类型模板参数包展开
1 2 3 4 5 6
| template<double... args> class A ... template<double First, double... Others> class A<First, Others...> : private A<Others...> ...
|
模板模板参数包展开
1 2 3 4 5 6
| template<typename T, template<typename> typename... Container> class A ... template<typename T, template<typename> typename FirstContainer, template<typename> typename... OthersContainer> class A<T, FirstContainer, OthersContainer...> : private A<OthersContainer...> ...
|
方式二:递归组合展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| template<typename... args> class A //泛化版本 ... template<typename First, typename... Others> class A<First, Others...> { public: A(First parf, Others... paro) : m_i(parf), m_o(paro...) { cout << m_i << endl; } First m_i; A<Others...> m_o; }
|
递归组合展开方式的思想是类的组合关系(一种包含关系)。
方式三:元组和递归调用展开
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33
| template<int count, int maxcount, typename... T> class A { public: static void func(const tuple<T...>& t) { cout << get<count>(t) << endl; A<count + 1, maxcount, T...>::func(t); } }
template<int maxcount, typename... T> class A<maxcount, maxcount, T...> { public: static void func(const tuple<T...>& t) { } }
template<typename... T> void functuple(const tuple<T...>& t) { A<0, sizeof...(T), T...>::func(t); }
int main() { tuple<float, int, int> mytuple(1.0f, 100, 12); functuple(mytuple); }
|
总结
第二章模板基础知识已经学了4小节了,我个人感受是很杂和多,而且有一点难度的,这种难度主要来自于缺乏码代码的实践经验。把书翻得差不多之后,还是要多写多练才行,在动手写代码的同时,遗忘的知识点可以回过来翻书翻资料,这样学习吸收效果较佳。