C++编码规范(模板编程)
C++2011标准允许函数模板带有缺省的类型参数,如果类型参数的缺省值与隐式推断的类型不一致,以隐式推断为准。
双引号下的字符串类型是指针 作为形参如果是传引用&则会将字符串作为一个整体来使用 如果直接传则被视为指针。
类模板的两步实例化
从类模板到对象实际上经历了两个实例化的过程:
- 编译期:编译器将类模板实例化为类并生成对象创建指令
- 运行期:处理器执行对象创建指令将类实例化为内存对象
类模板本身并不代表一个确定的类型,既不能用于定义对象,也不能用于声明指针或引用。只有通过模板实参将其实例化为具体的类以后,可具备类型语义。
/*模板Wrapper本身不能当作类型用,应该声明为 Wrapper<T>才是一个类型名称
当Wrapper当作函数名称用的时候就可以直接用比如构造函数的名称*/
#include<iostream>
using namespace std;
template<typename T>
class Wrapper{
public:
Wrapper(T const& t):m_t(t){}
//拷贝构造函数
Wrapper(Wrapper<T> const& that):
m_t(that.m_t){}
//拷贝赋值运算符
Wrapper<T>& operator=(Wrapper<T> const& rhs){
if(&rhs != this)
m_t = rhs.m_t;
return *this;
}
T m_t;
};
typename关键字
声明模板参数:template…,只有在这种语境下,typename关键字才可以和class关键字互换
class关键字:
可以声明类class A{…};
可以声明模板参数:template…
typename关键字:
可以声明模板参数:typename…
解决嵌套依赖:typename T::A a;
无论是声明模板参数还是解决嵌套依赖,都不能使用struct关键字
**嵌套依赖:**第一次编译模板代码时,模板参数具体类型尚不明确,编译器会把依赖于模板参数的嵌套类型理解为某个类的静态成员变量。因此当它看你到代码中使用这样的标识符声明变量时,会报告错误,叫做嵌套依赖。
typename关键字告诉编译器,所引用的标识符是个类型名,可以声明变量,具体类型等到实例化(二次编译)时再定
否则在第一次编译的时候会误认为是静态成员变量 无法定义变量 就会使编译器报错。
class X { public: class A {};};//A是X的嵌套类型
template<typename T>
void foo(void){
typename T::A a ...
}
foo<X> ();
template<typename T> void foo(void){ }
//如下例子:需要声明模板名是一个类型
template<typename T>
void print(List<T> const& li){
for(typename List<T>::const_iterator it = li.begin();
it!=li.end();++it)
cout << *it <<’’;
cout << endl;
}
template关键字
依赖模板参数的模板成员访问
在模板代码中,通过依赖于模板参数的对象、引用或指针访问其带有模板特性的成员,编译器常常因为无法正确理解模板参数列表的左右尖括号,而报告编译器错误。
class A
{
public:
template<typename T> void foo(void)
{ ... }
};
template<typename T> void foo(T& r,T* p)
{
r.foo<int>();
p->foo<int>();
} //错误
A a; foo(a,&a);
在模板名前加一个template关键字,意在告诉编译器其后的名称是一个模板,编译器就可以正确理解"<>"了。
#include<iostream>
using namespace std;
class A
{
public:
//普通类中的模板类型的成员函数
template<typename T>
void foo(void) const
{
T t;
//...
}
//普通类中的成员类模板
template<typename T>
class B
{
public:
T m_t;
};
};
//template告诉编译器后面的是一个模板类型,编译器就可以正确理解”< >”了
//否则编译器第一次编译无法确定foo是一个模板函数
template<typename T>
void bar(T const& t)
{
t.template foo<int>();
T const* p = &t;
p->template foo<double> ();
typename T::template B<string> b;
}
int main(void){
A a;
bar(a);
return 0;
}
子模板访问基类模板
在子类模板中直接访问那些依赖于模板的基类模板的成员,编译器在第一次编译时通常会因为基类类型不明确而只在子类或者全局作用域中搜索所引用的符号。
通过作用域限定符或者this指针迫使编译器去基类中的成员。
#include<iostream>
#include<cstdlib>
using namespace std;
//只要有template就是类型模板,子类必须也是模板
//模板第一次编译时,基类不明确,必须显示声明
template<typename T>
class A
{
public:
class B {};
void fun(void) {}
int m_var;
void exit(int states)
cout << "再见"<<endl;
};
template<typename T>
class C:public A<T> {
public:
void bar(void)
{
typename A<T>::B b;
A<T>::fun ();
//第一次编译的时候默认只在子类或者全局域中找
//this->fun(); 这么写也能达到目的,因为this表示肯定是成员变量,不可能是全局域
//但是子类中没有此成员,基类在第一次编译也不明确
//此时,编译器就会在第二次进行模板编译时确定基类this
A<T>::m_var = 10;
//this->m_var = 10;
exit(EXIT_SUCCEED);
//这里会自动调用标准C库里面的exit函数,将当前函数结束,编译不出错但是执行结果时不对的
}
};
int main(void){
C<int> c;
c.bar();
}
模板型成员变量
类模板的成员变量,如果其类型源自一个类模板的实例化的类,那么它就是一个模板型模板成员变量。
template<typename T> class List{
public:
void push_back(T const& elem){...}
};
template<typename T>class Stack{
public:
void push(T const& elem){ m_list.push_back(elem);}
private:
List<T> m_list;
}
#include<iostream>
using namespace std;
template<typename T>
class A{
public:
T m_t;
};
template<typename T>
class B{
public:
A<T> m_a; //类模板型成员变量
};
int main(void){
B<int> b;
cout << typeid(b.m_a.m_t).name() << endl; //I 整型
return 0;
}
模板类型成员函数:
类模板的成员函数,如果除了类模板的模板参数以外,还需要其他的模板参数,那么它就是一个模板成员函数.
template<typename T>class Stack
{
public:
template<typename E>
Stack(Stack<E> const& that){...}
template<typename E>
Stack<T>& operator= (Stack<E> const& rhs);
};
模板型模板参数:
零初始化:
未初始化的基本类型:
- 基本类型不存在缺省构造函数,没有被显示初始化的局部变量都具有一个不确定的值
int var; //未初始化(不确定的值) - 包含(自定义或者系统提供的)缺省构造函数的类,在未被显示初始化的情况下,都会有一个确定的缺省初始化状态。
Student var; //缺省初始化 调用缺省构造函数 - 这样会在模板的实现中产生不一致的语法语义
template void foo(void){
T var; //隐式缺省构造
cout << var << endl;
}
foo(); //不确定的值
#include<iostream>
using namespace std;
template<typename T>
void foo(void)
{
T t;
int I; //不初始化,没有构造函数,值未定义,内存中是啥就是啥
string s;//通过缺省构造函数初始化
T t = T(); //都是显式构造函数初始化 零初始化语法
int I = int();
string t = string();
cout << t << endl;
}
int main(void){
foo<int> ();
foo<string>();
return 0;
}
类模板中的虚函数
类模板的普通成员可以是虚函数,即可以为类模板定义虚成员函数。和普通类的虚成员函数一样,类模板的虚成员函数亦可以表现出多态性。
在某个类中 如果把某个成员函数指明为虚函数 前面加一个virtual关键字 那么在子类中和基类中相同原型的成员函数也是一个虚函数 通过一个指向子类对象的基类指针 或者 引用子类对象的基类引用去调用这个虚函数 实际执行的就是子类中的这个覆盖版的函数。
#include<iostream>
using namespace std;
template<typename T>
class A{
public:
virtual void foo(T const& t) const{
cout << "A::foo("<<t<<")"<<endl;
}
};
//只能是模板类的虚函数 但是虚函数不能是模板函数
class B:public A<X>
{
public:
template<typename X>
virtual void foo(X const& x) const{
cout<<"B::foo("<<x<<')'<<endl;
}
};
//虚函数和模板函数不能都是
class C
{
public:
template<typename X>
virtual void foo(X const& x) const{
cout<<"C::foo("<<x<<')'<<endl;
}
};
int main()
{
B<int> b;
A<int>& a = b;
a.foo(100); //子类
return 0;
}
无论是类还是类模板,其虚成员函数都不能是模板函数
基于虚函数的多态机制,需要一个名为虚函数表的函数指针数组。该数组在类被编译或类模板实例化的过程中产生,对象中虚指针指向这个数组。而此时那些模板形式的成员函数尚未被实例化,其入口地址和重载版本的个数,需要等到编译器处理完对该函数的所有调用以后才能确定。成员函数模板的延迟编译阻碍的虚函数表的静态构建。
模板的编译模型:
有意识的将一个类的函数声明和实现放在不同的文件中,易于维护并且方便协作开发,对于用户来说,不关心类的实现。
单一模型:
将模板的声明,定义和实例化放在单一的编译单元中,无论编译还是链接,其结果总是对的。
//cmp.h
#ifndef _CMP_H_
#define _CMP_H_
template<typename T> T max (T x,T y);
template<typename T> T min (T x,T y);
template<typename T> class Compator
{
public:
Comparator(T x, T y);
T max(void) const;
T min(void) const;
private:
T m_x,m_y;
};
#endif
//cmp.cpp
#include ”cmp.h”
template<typename T> T max (T x,T y){
return x < y ? y:x;
}
template<typename T> T min (T x,T y){
return x < y ? x:y;
}
template<typename T>
Comparator<T>::Compator(T x,T y):
m_x(x),m_y(y){}
template<typename T>
T Comparator<T>::max(void) const{
return m_x < m_y ? m_y :m_x;
}
template<typename T>
T Comparator<T>::min(void) const{
return m_x < m_y ? m_x :m_y;
}
//main.cpp
#include<iostream>
using namespace std;
#include “cmp.h”
int main(void)
{
int a = 123,b = 456;
cout <<::max(a,b)<<''
<<::min(a,b)<<endl;
double c = 1.23,d=4.56;
cout <<::max(c,d)<<''
<<::min(c,d)<<endl;
string e="hello",f="world";
cout <<::max(e,f)<<''
<<::min(e,df)<<endl;
Comparator<int> ci(a,b);
}
g++ -c cmp.cpp //编译
g++ -c main.cpp //编译
g++ cmp.o main.o //链接
nm 命令可以看一个二进制文件中符号
分离模型:
c/c++ 编译器每个文件单独编译的。编译器在编译模板定义文件时所生成的模板内部表示,此刻早已荡然无存。因此所有基于模板实例的类和函数,编译器只能假设它们被定义在其他模块中,并产生一个指向该(不存在的)定义的引用,期待链接器能在日后解决此问题。但事实上,由于模板内部表示并没有真正化身为可执行的可执行指令代码,链接器最终报告“未定义的引用”的错误。
包含模型:(头文件中#include<XXX.cpp>文件)
把模板定义文件包含在模板声明文件的内部(声明之后),即让模板的定义和声明都位于同一个头文件中。
任何希望使用模板的程序都必须包含模板的声明文件,而模板定义文件亦因包含而内嵌该声明文件,最终模板实例化代码同处于一个编译单元。
模板的声明,定义与实例化同处于一个编译单元中,编译器有能力对模板进行正确的实例化,没有任何链接错误。
包含模型会延长总体的编译时间,而且必须向模板用户提供模板的定义源文件。
实例模型:显式实例化 – 构建自己的项目的时候可以使用
在模板定义文件中使用实例化指示符,强制编译器在编译该文件时,即根据特定的模板参数对模板进行实例化
根据特定的模板参数对模板进行实例化的工作已经在模板定义文件中完成,使用该模板时无需再进行实例化,直接引用具体类型,调用具体函数即可
显示实例化的类型总是有限的,即便只考虑项目中有限类型的情况,也必须仔细跟踪每个需要实例化的类或者函数,这对于大型项目而言,将成为十分繁琐的工作
template<typename T>
T Comparator<T>::min(void) const{
return m_x < m_y ? m_x:m_y;
}
template int max<int> (int,int);
template double max<double> (double,double);
template string max<string> (string,string);
template int min<int> (int,int);
template double min<double> (double,double);
template string min<string> (string,string);
template class Comparator<int>;
template class Comparator<double>;
template class Compatator<string>;
预编译头文件:
g++ -c common.h
common.h.gch 为预编译头文件
可以缩短编译时间
编译器的内部状态:
- 当编译一个文件时,编译器从文件的开头一直扫描到文件的结束。对其所见到的每一个标记(包含来自头文件的标记),编译器都会更新其内部状态,以反映该标记所表达的语义,编译器的内部状态直接决定了其在目标文件中所产生的代码。
- 如果有多个需要编译的文件,其前N行代码完全相同,那么编译器在编译第一个文件时,就可以把与这N行代码相对应的内部状态保存在一个文件中。待其编译剩下的文件时,先从这个文件中重新加载事先保存好的内部状态,然后从N+1行开始编译。从一个文件中加载与N行代码向对应的内部状态,比实际编译N行要快得多。