C++编码规范系列 六(模板编程)

C++编码规范系列 六

C++编码规范(模板编程)

模板是类型参数化的一种实现。而宏定义只是在预处理器的作用下,实现替换,可以在某种程度上使程序的编写脱离类型的约束和限制,但是同时也丧失了类型的安全性。
但是可以通过宏定义构建通用的代码框架,让预处理器将其扩展为不同类型的具体版本,将宏的一般性和函数的类型安全性完美结合起来。

#define MAX(T) T max_##T(T x,T y){ \
return x >y ? x:y; }
MAX(int)
MAX(double)
MAX(string)
#define max(T) max_##T
cout << max(int)(123,456)<<endl;
cout << max(double)(1.23,4.56)<<endl;
cout << max(string)("hello","world")<<endl;

模板的实例化:通常情况下,并不是把模板编译成一个可以处理任何类型的单一实体,而是对于实例化的每一种类型,都会从模板中产生一个不同的实体。这种用具体类型代替模板参数的过程叫做实例化,会产生一个模板实例。
格式 函数模板名<类型实参1,类型实参2,…>(调用实参表);
函数模板的类型实参表放在尖括号中,调用实参表放在圆括号中,且类型实参表必须位于函数模板名和调用实参表之间,函数模板的类型实参表或者调用参数表可以为空,但是尖括号和圆括号不能不写。
普通函数只做一次编译,编译器看到函数定义的时候,会直接生成二进制代码,当编译器看到函数调用的时候会生成一个跳转指令,去调用函数的二进制代码。

二次编译,每个函数模板实际上都会被编译两次

  1. 一次在实例化之前,先检查模板代码本身,查看语法是否正确,因为不确定具体类型,无法生成二进制代码,只是转换成一个编译器能够识别的内部表示。
  2. 另一次是实例化期间,结合所使用的类型参数,再次检查模板代码,查看是否所有的调用都有效。
  3. 只有在第二次编译的时候才会产生二进制形式的机器指令。
  4. 第一次编译的结果,仅仅是在编译器的内部形成一个用于描述该模板的数据结构,即所谓模板的内部表示。
函数模板的隐式推断

如果函数模板调用参数的类型相关该模板参数,那么在调用该函数模板时,即使不显示制定模板参数,编译器也有能力根据调用参数的类型隐式推断出正确的模板参数,以获得与普通函数调用一致的语法表达。(不写尖括号)
以下三种情况没法隐式推断,必须显示指定模板参数:

  1. 不是全部模板参数都与调用参数的类型相关
  2. 隐式推断的同时不允许隐式类型转换 - 普通函数不存在推断
  3. 返回类型不能隐式推断
#include<iostream>
#include<typeinfo>  //在运行阶段获得类型信息
using namespace std;
template<typename T>
void foo(T const& x,T const& y){
	cout << typeid(x).name() <<''
	<< typeid(y).name() << endl;
}
template<typename T>
void bar(T x,T y){
	cout << typeid(x).name() <<''
	<< typeid(y).name() << endl;
}
template<typename R,typename T>
R foo(T const& x){
	R y;
	cout << typeid(x).name() <<''
	<< typeid(y).name() << endl;
	return y;
}

int main(void){
	int a,b;
	foo(a,b); // i i
	double c,d;
	foo(c,d); // d d
	chare[256],f[256]
	foo(e,f);//A256_c A256_c 传入参数为引用推断为数组
	bar(e,f);//Pc Pc 传数组推断成字符指针
}

注:数组名有二义性,某些场合下表示首元素地址,某些场合下表示数组整体

函数模板的重载

重载解析是在第二次编译的时候(调用期间)执行的,重载的备选方案列表是在第一次编译的时候,所以函数重载要保证函数模板在第一次编译的时候可见。
函数重载:同一个作用域下,函数名相同,参数列表不同.
重载解析是根据 1. 类型安全,函数一致性原则。 2. 最小工作量原则。 3. 模板重载有一条特殊优先原则,越具体越优先被选择重载。
普通函数和函数模板构成重载:普通函数和可实例化为该函数的模板构成重载关系。
在其他条件都相同的情况下,编译器优先选择普通函数,除非函数模板能够产生更好的匹配实例。

#include<cstdlib>
#include<cstring>
#include<iostream>
#include<typeinfo>
using namespace std;
//两个任意类型对象的最大值
template<typename T>
T const& max(T const& x,T const& y){
cout << "<1"<<typeid(x).name()<<'>'<<flush;
	return x < y ? y:x;
}
//引用的好处:避免在传参和返回的时候发生拷贝构造,效率低,对于指针对象可能需要考虑深拷贝

//求两个任意类型指针所指向目标的最大值
template<typename T>
T * const& max(T* const& x,T* const& y){
	cout << "<2"<<typeid(x).name()<<'>'<<flush;
	return *x< *y ? y:x;
}
//两个C风格字符串的最大值
//不使用引用作为参数,但是返回值用引用,会返回临时变量的引用
//临时变量在离开作用域后会将内存标记为自由,内存中的值就不稳定状态
//原则就是:要么全部使用引用,要么就都不用引用(调用拷贝构造),才避免出错
char const* const& max(char const* const& x,char const* const& y){
	cout << "<3"<<typeid(x).name()<<'>'<<flush;
	return strcmp(x,y)<0?y:x;
}
//三个任意类型对象的最大值
template<typename T>
T * const& max(T const& x,T const& y,T const& z){
	cout << "<4"<<typeid(x).name()<<'>'<<flush;
	return ::max(::max(x,y),z);
}

int main(void){
	char const* x = "abc";
	char const* y = "ab";
	char const* z = "a";
	//编译器优先选择普通函数,特殊优先
	cout << ::max(x,y)<<endl;
	//选择1版本 类型一致原则
	cout << ::max(123,456)<<endl;
	//<>表示一定是模板函数 会优先选择2版本 此方法之比较第一个字符
	cout<<::max<>(y,x)<<endl;
	//调用版本1 版本2的话会变成二级指针
    //显示指定的模板参数必须在所选择的重载版本中与调用的参数类型保持一致
	cout<<::max<char const*>(y,x)<<endl;
	cout << ::max(x,y,z)<<endl;//版本4调用版本3
	return 0;
}

重载的好处:重载关系和模板结合,依靠重载解析,就可以增加通用性。
普通函数和模板函数构成重载,编译器会优先选择普通函数;函数模板的隐式实例化不支持隐式类型转换,即使是函数模板的实例化函数中,编译器仍然坚持普通函数优先的原则,前提是普通函数在第一次编译时可见。

类模板
#include<iostream>
using namespace std;
//类模板
template<typename T>
class Comparator{
public :
	Comparator(T x,T y):m_x(x),m_y(y){}
	T max(void)const{
	return m_x < m_y ? m_y :m_x;
}
T min(void)const{
return m_x > m_y ? m_y :m_x;
     }
private:
	T m_x;
	T m_y;
};

//对应类型要重载> <运算符,否则在二次编译(调用)会报错
//一般运算符重载 <和==就基本覆盖所有运算符操作
class Integer{
public:
		Integer(int i):m_i(i){}
	bool operator<(Integer const& rhs) const{
		return m_i < rhs.m_i;
	}
	bool operator>(Integer const& rhs)const{
		return m_i > rhs.m_i;
	}
	friend ostream& operator<<(ostream& os, Integer const& i){
		return os << i.m_i;
	}
};

int main(void){
	Comparator<int> c1(123,456);
	cout << c1.max() << endl;
	Comparator<double> c2(1.23,45.6);
	cout << c2.max() << endl;
	Comparator<string> c3("hello","world");
	cout << c3.max() << endl;
return 0;
}

类模板中,只有那些被调用的成员函数才会被实例化,即产生实例化代码,某些类型虽然并没有提供类模板所需要的全部功能,但是依然可以实例化该模板,只要不直接或者间接调用那些依赖于未提供功能的成员函数。

类模板的语法形式:

-template <typename 类型参数1,typename 类型参数2,...>

在类模板的内部,类型参数可以象其他任何具体类型一样,用于成员变量,成员函数,成员类型(内部类型),甚至是基类的声明(逆多态 – B 子类匹配不同的基类,基类参数化)

template<typename M,typename R,typename A
typename V,typename T,typename B>
class MyClass:public B{
	M m_mem;
	R function(a arg){ ... V var ...}
	typedef T* pointer;
};

类模板两步实例化:
编译期:编译器将类模板的实参传递给类模板的形参,并实例化为类并生成对象创建指令(分配内存,构造函数调用)称为类模板的实例化.
运行期:处理器执行对象创建指令并将类实例化为内存对象.

类模板本身并不能代表一个确定的类型,既不能用于定义对象,也不能用于声明指针或者引用。只有通过模板实参将其实例化为具体类以后,可具备类型语义。
类模板不能做隐式推断。只有函数模板能做隐式推断。
类模板的静态成员变量,既不是一个对象一份,也不是一个模板一份,而是在该类模板的每个实例化类中,各有一份独立的拷贝,且为该类所有实例化对象所共享。
合法作用域:全局域,类,函数,大括号语句
静态成员变量在类里面声明,普通成员在构造函数中定义和初始化,静态成员变量在类外进行定义或初始化。

#include<iostream>
using namespace std;
template<typename T>
class A{
public:
	static T m_x;
	static int m_y;
} ;
//无论T是什么类型,都会使用缺省构造函数或者基本类型的默认值来初始化
template<typename T>
T A<T>::m_x = T(); 

template<typename T>
int A<T>::m_y = 0;

int main(void){
	A<int> a,b;
	cout << &a.m_x << ' '<<&b.m_x << endl; //同样的地址,共用一份
	A<double> c,d;
	cout << &c.m_x << ' '<<&d.m_x << endl; //跟上面的不一样的类
	return 0;
}

类模板的递归实例化:
类模板的类型实参可以是任何类型,只要该类型能够提供模板所需要的功能。模板自身的实例化类也可以实例化自身,称为递归实例化。通过这种方法可以很容易的搭建在空间上具有递归特性的数据结构。

#include<iostream>
using namespace std;
class Array{
public:
//数据结构需要实现下标访问,下标访问
/* int& operator[] (size_t i) const{ 中的const是修饰this,表示this指向的对象是不可修改的*/
//不加const 则private的数组可以被修改 
//用户可以自己选择是否const 可以限定修改的权限 会自动调用对应的下标运算符重载
int& operator[] (size_t i){
	return m_array[i];
}
int const& operator[](size_t i)const{
	return m_array[i];
}
//上述出现代码重复,改为下面,const只调用const方法,所以要去常转换
int const& operator[](size_t i)const{
	return const_cast<Array&>(*this)[i];
}
private:
	int m_array[3];
};

//使用引用,避免拷贝构造函数,提高效率,但是可能会修改里面的值,容易造成失误错误,所以使用const 
//const对象只能调用const函数 ,const指针不能隐式转换成普通指针,因为访问范围扩大了。
//所以对下标运算符的重载一般都有两个版本一个const版本拒绝做修改一个普通版本
void print(Array const& array){
	for(size_t I = 0; i< 3;i++)
		cout << array[i] << ' ';
	cout << endl;
}

int main(void){
Array a1;
a1[0] = 100; /*编译器翻译: a1.operator[] (0) = 100; 函数名为operator[] 函数的参数是下标 返回是数组中对应下标的引用*/
a1[1] = 100;
a1[2] = 100;
print(a1);
Array<Array<int> > a3; //3X3二维数组
}

//转换成模板类
#include<iostream>
using namespace std;
template<typename T>
class Array{
public:
T& operator[] (size_t i){
	return m_array[i];
}

T const& operator[](size_t i)const{
	return const_cast<Array&>(*this)[i];
}
//重载输出运算符 cout<<a3<<endl;
friend ostream& operator<< (ostream& os,
Array const& arrray){
	for(size_t i = 0;i < 3; i++)
		os << '(' << array.m_array[i] << ')';
	return os;
}
private:
	T m_array[3];
};

//template<typename T>class Array{  ... }
//template<typename T>class List{ ... }
//List<Array<int> > a; //数组链表
//Array<List<int> > b; //链表数组
//Array<Array<int> >c; //二维数组
//List<List<int> > d; //十字链表

类模板的特化:针对某些特殊类型进行单独处理
函数可以重载,类不可以重载,但是模板可以特化

/*下面比较会出现问题就是 char const* 类型时会执行对指针的比较而不是对字符的比较*/
#include<iostream>
using namespace std;
//类模板
template<typename T>
class Comparator{
public :
	Comparator(T x,T y):m_x(x),m_y(y){}
T max(void)const{
	return m_x < m_y ? m_y :m_x;
	}
	
T min(void)const{
	return m_x > m_y ? m_y :m_x;
     }
private:
	T m_x;
	T m_y;
};
//所以这里对char const* 类型进行模板的全类特化处理
//类的全特化的实现可以自定义
template<>
class Comparator<char const*>{
	private :
		char const* m_x;
		char const* m_b;
	public:
		Comparator(char const* a,char const* b):
			m_a(a),m_b(b){}
		char const* ZuidaZhi(void) const{
			return strcmp(m_a,m_b) < 0 ? m_b:m_a;
		}
		char const* ZuiXiaoZhi(void) const{
			return strcmp(m_a,m_b) > 0 ? m_b:m_a;
		}
}; 

//comparator模板针对char const* 类型的成员特化
//特化函数的声明和通用函数的声明一样的,规则不能变
template<>
char const* Comparator<char const*>::max(void)const{
	return strcmp(m_x,m_y)<0?m_y:m_x;
}

template<>
char const* Comparator<char const*>::min(void) const{
	return strcmp(m_x,m_y)<0?m_x:m_y;
}

类模板的局部特化:
一方面为类模板指定特定的实现,另一方面又允许用户对部分模板参数自行设定

#include<iostream>
#include<cstdlib>
using namespace std;
//通用版本
template<typename A,typename B>
class C {
	public:
		static void type(void){
			cout << "C<A,B>"<< endl;
	}
};
//想固定B的类型,但是A是任意的
//针对B类型参数取short的局部特化
template<typename A>
class C<A,short>{
	public:
		static void type(void){
			cout << “C<A,short><<endl;
	} 
};
int main(void){
	C<int,char>::type();
	C<int,short>::type();
	return 0;
}
结果:C<A,B>
     C<A,short>
//另外一种局部特化,当两个参数取类型相同的时候
template<typename A>
class C<A,A>{
	public:
		static void type(void){
			cout << "C<A,A>"<<endl;
	}
};
//针对两个类型参数都取指针的局部特化版本
template<typename A,typenameB>
class C<A*,B*>{
	public:
		static void type(void){
			cout << "C<A*,B*>"<<endl;
	}
};

//C<int*,int*> 调用时会报错,因为既满足同类型模板 又满足指针类型模板,编译器认为有歧义
/*如果多个局部特化同等程度的匹配某个声明,那么该声明将会因为二义性而导致歧义错误*/
//针对两个类型参数都取相同指针的局部特化版本
template<typename A >
class C<A*,A*>{
	public:
		static void type(void){
			cout << "C<A*,A*>"<<endl;
		}
};

//针对两个参数都取引用的局部特化版本
template<typename A,typename B>
class C<A&,B&>{
	public :
		static void type(void){
			cout << "C<A&,B&>"<<endl;
		}
};

//针对数组的特化
template<typename A,typename B>
class C<A[],B[]>{
	public:
		static void type(void){
			cout << "c<A[],B[]>"<< endl;
		}
};

class Dummy
{
	public:
		Dummy(void){
			cout << “Dummy构造”<<endl;
		}
	
		~Dummy(void){
			cout << “Dummy析构”<<endl;
		}
};
//这个模板,如果是参数类型是数组就会出现核心转储,吐核
template<typename T>
void foo(T* p){
	//...
	delete p;
}

/*C++98里面只有类模板能做局部特化,没有针对函数的局部特化(避免和重载规则发生冲突),所以下面是不允许的*/
template<typename T>
void foo<T[]>(T* p){
	//...
	delete[] p;
}

//将函数改成一个类,再进行特化
template<typename T>
class Foo{
public:
	static void foo(T* p){
		//...
		delete p;
	}
};

template<typename T>
class Foo<T[]>{
public:
		static void foo(T* p){
			//...
			delete[] p;
		}
};

类模板参数的缺省值:
类模板的模板参数可以带有缺省值,即缺省模板实参
实例化类模板是,如果提供了模板实参则用所提供的模板实参实例化相应的模板形参,如果没有提供模板实参则相应的模板形参取缺省值
例如:

template<typename A = int,typename B = double, typename C = string> ClassX {...}; 
X<char,short> x1(...); //X<char,short,string> x1(...)
X<char> x2(...);//X <char,double,string> x2(...)
X<> x3(...); //X<int,double,string> x3(...)

如果类模板的某个参数带有缺省值,那么它后面的所有模板参数都必须要带缺省值

类模板后面的参数的缺省值可以引用前面的参数值
(函数的缺省参数是不能引用前面的参数的,函数参数缺省值绝对不能用局部变量,局部变量的作用域作用于函数,缺省值是在函数之外在调用者中进行压栈操作的)

template<typename A,typename B = A*>
Class X {...};
X<int> x; //等价于X<int,int*> x

非类型模板参数:
(比如数组的长度,如果定义成指针动态申请数组长度的话,就需要重载拷贝构造函数执行深拷贝,因为缺省的拷贝构造函数是只进行指针拷贝的)

template <typename T = int, size_t S = 3 >
/*S只能用常量表达式不能用变量,但是const属性变量是可以的,编译器会进行常量优化直接用常量进行替换,不可以const volatile(挥发性),编译器会不进行优化,正常进行栈操作,在模板实例化时就无法别识别*/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值