模板编程(Template Programming)
模板编程
自动推导类型
auto
使用auto需注意以下几个事项:
- auto 声明的变量必须在定义时初始化
- 初始化的右值可以是具体的数值,也可以是表达式和函数的返回值等。
- auto不能作为函数的形参。
- auto不能直接声明数组。
- auto不能定于类的非静态成员函数。
auto常用于:
- 代替冗长复杂的变量声明。
- 在模板中,用于声明依赖模板参数的变量。
- 函数模板依赖模板参数的返回值。
- 用于lambda表达式。
函数模板
用一个样例就可简单解释它的用法:
template<typename T>
void Swap(T& a,T& b)
{
T temp = a;
a = b;
b = temp;
}
函数模板会根据我们输入的参数类型自动推导类型T,从而生成对应的代码。
有个小细节需要注意,在C++98时使用 class来推导参数,选择可以选择typename和class,建议在函数模板中使用typename,类模板中使用class。
以下是函数模板的注意事项:
- 可以为类的成员函数创建模板,但不能是虚函数和析构函数。
- 使用函数模板时,必须明确数据类型,确保实参与函数模板能匹配上。
- 使用函数模板时,推导的数据类型必须适应函数模板中的代码。
- 如果是自动类型推导,不会发生隐式类型转换,如果显式指定了函数模板的数据类型,可以发生隐式类型转换。
- 函数模板支持多个通用数据类型为的参数。
- 函数模板支持重载,可以有非通用数据类型的参数。
解释一下第四条:
template<typename T>
T Add(const T& a,const T& b)
{
return a + b;
}
int a = 10;
int b = 20;
char c = 'a';
std::cout<<add(a,b)<<std::endl; //out 30
std::cout<<add(a,c)<<std::endl; //error
std::cout<<add<int>(a,c)<<std::endl; //out 107
函数模板的具体化(函数模板特化)
可以提供一个具体化的函数定义,当编译器找到与函数调用匹配的具体化定义时,将使用该定义,不再寻找模板。这个方法有时用来做类型判断。
template<typename T>
void isInt(T a)
{
std::cout << "The type isn't int" << std::endl;
//...do something
}
template<> void isInt(int a)
{
std::cout << "The type is int" << std::endl;
//...do something
}
int a = 1;
std::string b = "string";
isInt(a); //out:The type is int
isInt(b); //out:The type isn't int
关于一些命名规则:对于给定的函数名,可以有普通函数、函数模板和具体化的函数模板,以及它们的重载版本。
编译器使用各种函数的规则:
- 具体化优先于常规模板,可以用空模板参数强制使用函数模板。
- 如果希望使用函数模板,可以用空模板参数强制使用函数模板。
- 如果函数模板能产生更好的匹配,将优先于非模板函数。
函数模板分文件编写
函数模板只是函数的描述,没有实体,创建函数模板的代码放在头文件中。
函数模板的具体化有实体,编译的原理和普通函数一样,所以,声明放在头文件中,定义放在源文件中。
函数模板-函数模板高级
$decltype$关键字
C++11引入关键字:$decltype$ 用于查询表达式的数据类型。
decltype(expression) var;
decltype的推导规则:
- 如果 $expression$ 是一个没有用括号括起来的标识符,则 $var$ 的类型与该标识符的类型相同,包括 $const$ 等限定符。
- 如果 $expression$ 是一个函数调用,则 $var$ 的类型与函数的返回值类型相同(函数不能返回void,但能返回void *)。
- 如果 $expression$ 是一个左值(能取地址且在第一种情况之外)、或者用括号括起来的标识符,那么 $var$ 的类型是 $expression$ 的引用。
- 如果均不满足上述条件,那么 $var$ 的类型与 $expression$ 相同。
如下:
void test()
{
int a = 10;
decltype(a) dec_a; //int
decltype((a)) dec_a_ = a; //int&
int* b = &a;
decltype(b) dec_b; //int *
int& c = a;
decltype(c) dec_c; //error:decltype之会返回类型,所以别忘了引用类型必须初始化
decltype(func()) dec_func; //int,并且不会调用func()
decltype(func) dec_func_name; //int()
decltype(func) *dec_func_pointer = func; //int(*)()
dec_func_pointer(); //out:func
decltype((func)) dec_func_ = func; //int(&)()
dec_func_(); //out:func
}
decltype与auto的区别
decltype不会执行表达式,而auto会,以上面的func举例:
auto auto_a = func(); //out:func
decltype(func()) dec_func; //nothing output
函数后置返回类型
int func(int x, double y);
等同于:
auto func(int x, double y) -> int;
可以理解为 $auto$ 在其中的作用为一个占用符,函数定义也可以这么写。
注:从C++14后,如下可以省略decltype
template<typename T1, typename T2>
auto func(T1 x, T2 y) -> decltype(x+y)
{
decltype(x+y) temp = x + y;
return temp;
}
可省略为:
template<typename T1, typename T2>
auto func(T1 x, T2 y)
{
decltype(x+y) temp = x + y;
return temp;
}
类模板
template<class T1, class T2>
class Entity
{
private:
T1 m_t1;
T2 m_t2;
public:
Entity(T1 t1, T2 t2)
:m_t1(t1),m_t2(t2)
{
...
}
...
}
函数模板建议用 $typename$ 描述通用数据类型,类模板建议使用 $class$。
注:
- 在创建对象的时候,必须指明具体的数据类型。
- 使用类模板时,数据类型必须适应类模板中的代码。
- 类模板可以为通用参数指定缺省的数据类型(C++11标准的函数模板也可以)
template<class T1, class T2 = std::string> //当Entity<int> entity; 中T2选择默认的string参数
class Entity
{
private:
T1 m_t1;
T2 m_t2;
public:
Entity(T1 t1, T2 t2)
:m_t1(t1),m_t2(t2)
{
...
}
...
}
- 类的成员函数可以在类外实现(但是不能在类外部指定默认参数,这是一个UB行为)。
- 可以用 $new$ 创建模板对象(但必须指明参数类型)。
- 在程序中,模板类的成员函数使用了才会创建。