Unit04 类模板
01 类模板的声明
-
形式:template<class 类型形参1, …> class 类模板名 { … };
例如:
template<class A, class B> class CMath { public: A m_a; B func() { ... }; };
-
如果在类模板外实现成员函数
template<class 类型形参1, ...> 返回值类型 类模板名<类型形参1, ...>::函数名(调用形参1, ...) { 函数体实现; }
例如:
template<class A, class B> B Cmath<A, B>::func() { ....; }
-
实例:
#include <iostream> using namespace std; //类模板 template<class T> class CMath { public: CMath(T const& t1, T const& t2) : m_t1(t1), m_t2(t2) {} // T add(); //声明实现写一起 T add() { return m_t1 + m_t2; } private: T m_t1; T m_t2; }; //声明实现分开写 // template<class T> // T CMath<T>::add() // { // return m_t1 + m_t2; // } int main() { return 0; }
02 类模板的使用
-
使用类模板必须对类模板进行实例化(产生真正的类)
类模板本身并不代表一个确定的类型(即不能用与定义对象),只有通过类型实参实例化成真正的类后才具备类的语义(即可以定义对象)。
例如:
CMath<int, double> math;
#include <iostream>
using namespace std;
//类模板
template<class T>
class CMath
{
public:
CMath(T const& t1, T const& t2) : m_t1(t1), m_t2(t2) {}
// T add();
//声明实现写一起
T add() {
return m_t1 + m_t2;
}
private:
T m_t1;
T m_t2;
};
//声明实现分开写
// template<class T>
// T CMath<T>::add()
// {
// return m_t1 + m_t2;
// }
int main()
{
int nx = 10, ny = 20;
CMath<int> m1(nx, ny);
cout << m1.add() << endl;
double dx = 12.3, dy = 45.6;
CMath<double> m2(dx, dy);
cout << m2.add() << endl;
string sx = "hello", sy = " world";
CMath<string> m3(sx, sy);
cout << m3.add() << endl;
return 0;
}
-
类模板被实例化时类模板中的成员函数并没有实例化,成员函数只有在被调用时才会被实例化(即产生真正成员函数)注意:成员虚函数除外。
-
某些类型虽然并没有提供类模板所需要的全部功能但照样可以实例化类模板,只要不调用那些未提供功能的成员函数即可。
03 类模板的静态成员
- 类模板中的静态成员即不是每个对象拥有一份。
- 也不是类模板拥有一份。
- 而应该是由类模板实例化出的每一个真正的类个有一份。
- 且为该类实例化类定义的所有对象共享。
#include <iostream>
using namespace std;
template<class T>
class A
{
public:
static void print() {
cout << "&m_i:" << &m_i;
cout << ", &m_t:" << &m_t << endl;
}
static int m_i;
static T m_t;
};
template<class T>int A<T>::m_i; // = 0;
template<class T>T A<T>::m_t; //= ??
int main()
{
A<int> x, y, z;
x.print();
y.print();
z.print();
A<int>::print();
cout << endl;
A<double> m, n, t;
m.print();
n.print();
t.print();
A<double>::print();
return 0;
}
04 类模板的递归实例化
- 可以使用任何类型来实例化类模板。
- 由类模板实例化产生的类也可以用来实例化类模板自身,这种做法称之为类模板的递归实例化。
- 通过这种方法可以构建空间上具有递归特性的数据结构(例如:多维数组)。
#include <iostream>
using namespace std;
template<class T>
class Array
{
public:
T& operator[](size_t i) {
return m_arr[i];
}
private:
T m_arr[10];
};
int main()
{
Array< Array<int> > m;
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
m[i][j] = i + j;
}
}
for (int i = 0; i < 10; i++) {
for (int j = 0; j < 10; j++) {
cout << m[i][j] << '\t';
}
cout << endl;
}
return 0;
}
Unit05 类模板扩展
01 全局特化
-
全类特化:特化一个类模板可以特化该类模板所有的成员函数,相当于重新写了一个针对某种特定数据类型的具体类
声明形式:
template<>class 类模板名<类型参数1, ...> {...}
例如:
template<>class CMath<char*> {...}
-
成员特化:类模板特化除了可以对整个类进行特化以外,可以只针对某部分成员函数进行特化。
声明形式:
template<>返回值类型 类模板名<类型参数1, ...>::成员函数名(调用参数1, ...) {...}
例如:
template<> char* const CMath<char* const>::add(...) {...}
#include <iostream>
#include <cstring>
using namespace std;
template<class T>
class CMath
{
public:
CMath(const T& t1, const T& t2) : m_t1(t1), m_t2(t2) {}
T add() {
return m_t1 + m_t2;
}
private:
T m_t1;
T m_t2;
};
//成员特化
template<>
char* const CMath<char* const>::add() {
return strcat(m_t1, m_t2);
}
/*
//全类特化
template<>
class CMath<char* const>
{
public:
CMath<char* const>(char* const& t1, char* const& t2) : m_t1(t1), m_t2(t2) {}
char* const add() {
return strcat(m_t1, m_t2);
}
private:
char* const m_t1;
char* const m_t2;
};
*/
int main()
{
char cx[256] = "hello", cy[256] = " world";
CMath<char* const> m(cx, cy);
cout << m.add() << endl;
return 0;
}
何时使用:当一个模板类中的某些成员函数中的操作针对某些数据类型无法执行时,使用全局特化来特殊处理这种数据类型实例化出来的具体类。其中全局特化的工作量较大,成员特化可以只针对那些无法执行的成员函数进行重写。
02 局部特化
- 类模板的局部特化,除非必要否则尽量不要特化,因为特化版本过多容易引发编译器匹配歧义。
#include <iostream>
using namespace std;
template<class T, class D>class CMath {
public:
static void foo() {
cout << "1:CMath<T, D>::foo" << endl;
}
};
//局部特化1
template<class T>class CMath<T, short> {
public:
static void foo() {
cout << "2:CMath<T, short>::foo" << endl;
}
};
//局部特化2
template<class T>class CMath<T, T> {
public:
static void foo() {
cout << "3:CMath<T, T>::foo" << endl;
}
};
//局部特化3
template<class T, class D>class CMath<T*, D*> {
public:
static void foo() {
cout << "4:CMath<T*, D*>::foo" << endl;
}
};
int main()
{
CMath<int, double>::foo(); //1
CMath<int, short>::foo(); //2
//CMath<short, short>::foo(); //匹配歧义
CMath<int*, double*>::foo(); //1
CMath<int*, int*>::foo(); //匹配歧义
return 0;
}
03 类型形参的缺省
-
类模板的类型形参可以带缺省值。
实例化类模板时,如果提供了类型实参则用所提供的类型实参来实例化类模板,如果没有提供类型实参则用相应的类型形参的缺省类型来实例化类模板。
-
如果类模板的某个类型形参带有缺省值,那么它后面的类型形参都必须带缺省值。
#include <iostream>
#include <typeinfo>
using namespace std;
template<class T=short, class D=int>class CMath {
public:
void print() {
cout << "m_t:" << typeid(m_t).name() << ", "
<< "m_d:" << typeid(m_d).name() << endl;
}
private:
T m_t;
D m_d;
};
int main()
{
CMath<float, double> m;
m.print();
CMath<> m2;
m2.print();
return 0;
}
04 数值形的模板参数
- 类模板的模板形参并不限于类型参数,不同数值也可以作为模板的参数。
#include <iostream>
using namespace std;
template<class T=double, size_t S=15>class Array {
public:
T& operator[](size_t i) {
return m_arr[i];
}
size_t size() {
return S;
}
private:
T m_arr[S];
};
int main()
{
Array<int, 20> a;
for (int i = 0; i < a.size(); i++) {
a[i] = i + 1;
}
for (int i = 0; i < a.size(); i++) {
cout << a[i] << " ";
}
cout << endl;
return 0;
}
05 模板技巧
01 模板型成员变量
成员变量,但其类型是由一个类模板实例化的未知类,那么它才可以称之为模板型成员变量
例如:
template<class T>class Array {...};
template<class D>class Sum {
public:
Array<D> m_s; //模板型成员变量
};
#include <iostream>
using namespace std;
template<class T>class Array {
public:
T& operator[](size_t i) {
return m_arr[i];
}
private:
T m_arr[10];
};
template<class D>class Sum { //求和器
public:
Sum(Array<D>& s) : m_s(s) {}
D add() {
D d = 0;
for (int i = 0; i < 10; i++) {
d += m_s[i];
}
return d;
}
private:
Array<D> m_s; //模板型成员变量
};
int main()
{
Array<int> a;
for (int i = 0; i < 10; i++) {
a[i] = i + 1;
}
Sum<int> s(a);
cout << s.add() << endl;
return 0;
}
02 模板型成员函数
类模板的成员函数模板
例如:
template<class T>class CMath {
public:
template<class D>void foo() {...} //成员函数模板
};
如果在类外实现:
template<class T>template<class D>void CMath<T>::foo() {...}
#include <iostream>
using namespace std;
template<class T>class CMath {
public:
template<class D>void foo();
// template<class D>void foo() { //成员 函数模板
// cout << "CMath<T>::foo<D>()" << endl;
// }
};
template<class T>
template<class D>void CMath<T>::foo()
{
cout << "CMath<T>::foo<D>()" << endl;
}
int main()
{
CMath<int> m;
m.foo<double>();
return 0;
}
03 模板型成员类型
类模板中嵌套的类模板
例如:
template<class X>class A {
public:
template<class Y>class B {...}; //模板型成员类型
};
#include <iostream>
using namespace std;
template<class X>class A {
public:
template<class Y>class B {
public:
template<class Z>class C;
};
};
template<class X>
template<class Y>
template<class Z>class A<X>::B<Y>::C {
public:
template<class T>void foo() {
cout << "foo()" << endl;
}
};
int main()
{
A<int>::B<double>::C<float> c;
c.foo<string>();
return 0;
}
04 模板型模板参数
类模板的模板形参也可以是类模板,可以有缺省值
例如:
template<class T>class Array {...};
template< template<class D>class C=Array >class Sum {
...
};
#include <iostream>
using namespace std;
template<class T>class Array {
public:
T& operator[](size_t i) {
return m_arr[i];
}
private:
T m_arr[10];
};
template<class D, template<class M>class C>class Sum {
public:
Sum(C<D>& s) : m_s(s) {}
D add() {
D d = 0;
for (int i = 0; i < 10; i++) {
d += m_s[i];
}
return d;
}
private:
C<D> m_s;
};
int main()
{
Array<int> a;
for (int i = 0; i < 10; i++) {
a[i] = i + 1;
}
Sum<int, Array> s(a);
cout << s.add() << endl;
return 0;
}
Unit06 模板典型错误
01 嵌套依赖
-
问题:
由于模板要经过两次编译,在第一次编译模板的代码时,类型形参的具体类型尚不明确,编译器将把类型形参的嵌套类型理解为某个未知类型的静态成员变量,因此编译器看到使用这样的标识符声明变量时会报告错误,这就叫嵌套依赖。
-
解决方法:
在类型形参的前面增加一个typename标识符,意在告诉编译器其后时一个类模板的嵌套使用。
#include <iostream>
using namespace std;
class A {
public:
class B {
public:
void foo() {
cout << "A::B::foo()" << endl;
}
};
};
template<class T>void Func() {
typename T::B b; //嵌套依赖
b.foo();
}
int main()
{
Func<A>();
return 0;
}
02 依赖模板参数访问成员函数模板
-
问题:
利用未知类定义的对象来访问成员函数模板时,编译器在第一次编译时无法解析成员函数模板的类型参数列表的 <> 而报告编译错误。
-
解决方法:
在成员函数模板之前增加template关键字,意在告诉编译器其后是一个函数模板实例,编译器就可以正确理解 <> 了。
#include <iostream>
using namespace std;
class A {
public:
template<class T>void foo() {
cout << "A::foo()" << endl;
}
};
template<class D>void Func() {
D d;
d.template foo<int>(); //依赖模板参数访问成员函数模板
}
int main()
{
Func<A>();
return 0;
}
03 子模板访问基模板
-
问题:
在子类模板中访问基类模板的成员,编译器第一次编译时只在子类模板和全局域中搜索使用的标识符号,不会到基类模板中搜索。
-
解决方法:
在子类模板中可以通过使用作用域限定符或显示使用
this
指针。
#include <iostream>
using namespace std;
template<class T>class Base {
public:
int m_i;
void foo() {
cout << "Base<T>::foo()" << endl;
}
};
template<class T, class D>class Derived : public Base<T> {
public:
void bar() {
Base<T>::m_i = 100;
Base<T>::foo();
// this->m_i = 100;
// this->foo();
}
};
int main()
{
Derived<int, double> d;
d.foo();
return 0;
}
04 零值初始化
-
问题:
基本类型不存在缺省构造函数,未被初始化的局部变量都具有一个不确定的值(int a;//值不确定)
类类型由于存在缺省构造函数,在未被初始化的情况下可以有一个确定的缺省初始化状态。(Integer a;//值确定)
基于以上两点,就会在模板实现中产生不一致的语法语义。
-
解决方法:
如果希望模板中,所有类型参数的变量,无论是类类型还是基本类型都以缺省方式获得初始化,就必须对其进行显示缺省构造
T()
。
#include <iostream>
using namespace std;
class Integer {
public:
Integer() : m_i(0) {}
private:
int m_i;
friend ostream& operator<<(ostream& os, const Integer& that);
};
ostream& operator<<(ostream& os, const Integer& that) {
return os << that.m_i;
}
template<class T>void Func() {
T t = T(); //Integer() / int()
cout << "t=" << t << endl;
}
int main()
{
Func<int>();
Func<Integer>();
return 0;
}
05 类模板中的成员虚函数
-
类模板中的普通成员函数可以是虚函数
即可以为类定义成员虚函数,和普通类的成员虚函数一样,类模板的成员虚函数也可以表现出多态性。
-
类模板中的成员函数模板不可以是虚函数
根据成员虚函数的多态机制,需要一个虚函数表(表中保存成员虚函数的入口地址),而这个表是编译器在实例化类模板时就产生,类的成员函数模板的实例化(即产生真正的函数实体)需要编译器处理完调用后才会完成,这是才出现成员虚函数的地址。
-
总结:
成员函数模板的延迟编译阻碍了虚函数表的静态构建。
#include <iostream>
using namespace std;
template<class T>class Base {
public:
virtual void foo() {
cout << "Base<T>::foo()" << endl;
}
template<class M>void bar() {
}
};
template<class T, class D>class Derived : public Base<T> {
public:
virtual void foo() {
cout << "Derived<T, D>::foo()" << endl;
}
};
int main()
{
Derived<int, double> d;
Base<int>* pBase = &d;
pBase->foo();
pBase->bar<int>();
return 0;
}