模板
1. 函数模板 (Function Templates)
假设我们有如下的函数模板
template <typename T> // this is the template parameter declaration
const T& max(const T& x, const T& y)
{
return (x > y) ? x : y;
}
然后, 当我们编译程序时,编译器遇到了一个对函数模板的调用
int i = max(3, 7); // calls max(int, int)
编译器就会复制函数模版并且创造了一个新的模版实例 max(int, int)
const int& max(const int &x, const int &y)
{
return (x > y) ? x : y;
}
这就是所谓的 函数模板实例 (Function Template Instance)。
1.1. 运算符(Operators),函数调用(function calls)和函数模版 (function templates)
正因为函数模版会被复制并赋予所需的参数类型从而转化成正常函数,而参数类型没有任何限制,所以会出现对运算符对某些参数类型未定义的情况。
例如,我们有一个类
class Cents
{
private:
int m_cents;
public:
Cents(int cents)
: m_cents(cents)
{
}
};
然后我们用一个函数模板 max()
调用
template <typename T> // this is the template parameter declaration
const T& max(const T& x, const T& y)
{
return (x > y) ? x : y;
}
class Cents
{
private:
int m_cents;
public:
Cents(int cents)
: m_cents(cents)
{
}
};
int main()
{
Cents nickle(5);
Cents dime(10);
Cents bigger = max(nickle, dime);
return 0;
}
C++ 会产生一个模板实例
const Cents& max(const Cents &x, const Cents &y)
{
return (x > y) ? x : y;
}
但是 C++ 并不知道如何比较这两个实例的大小,因为运算符并没有被定义。
解决办法就是,我们重载一下 >
这个运算符
class Cents
{
private:
int m_cents;
public:
Cents(int cents)
: m_cents(cents)
{
}
friend bool operator>(const Cents &c1, const Cents &c2)
{
return (c1.m_cents > c2.m_cents);
}
};
类似的,我们可以重载<<
+=
/=
,最终我们完整代码如下:
class Cents
{
private:
int m_cents;
public:
Cents(int cents)
: m_cents(cents)
{
}
friend bool operator>(const Cents &c1, const Cents &c2)
{
return (c1.m_cents > c2.m_cents);
}
friend std::ostream& operator<< (std::ostream &out, const Cents ¢s)
{
out << cents.m_cents << " cents ";
return out;
}
Cents& operator+=(Cents cents)
{
m_cents += cents.m_cents;
return *this;
}
Cents& operator/=(int value)
{
m_cents /= value;
return *this;
}
};
template <class T>
T average(T *array, int length)
{
T sum = 0;
for (int count=0; count < length; ++count)
sum += array[count];
sum /= length;
return sum;
}
int main()
{
Cents array3[] = { Cents(5), Cents(10), Cents(15), Cents(14) };
std::cout << average(array3, 4) << '\n';
return 0;
}
2. 类模板 (Class Templates)
我们可以在 Array.h
里面定义一个类模板然后在 main.cpp
中调用。
#ifndef Array_h
#define Array_h
#include <assert.h> // for assert()
template <class T> // This is a template class, the user will provide the data type for T
class Array
{
private:
int m_length;
T *m_data;
public:
Array()
{
m_length = 0;
m_data = nullptr;
}
Array(int length)
{
m_data = new T[length];
m_length = length;
}
~Array()
{
delete[] m_data;
}
void Erase()
{
delete[] m_data;
// We need to make sure we set m_data to 0 here, otherwise it will
// be left pointing at deallocated memory!
m_data = nullptr;
m_length = 0;
}
T& operator[](int index)
{
assert(index >= 0 && index < m_length);
return m_data[index];
}
// The length of the array is always an integer
// It does not depend on the data type of the array
int getLength(); // templated getLength() function defined below
};
template <typename T> // member functions defined outside the class need their own template declaration
int Array<T>::getLength() { return m_length; } // note class name is Array<T>, not Array
#endif /* Array_h */
类模板的实例化过程和模板函数一模一样:编译器根据程序需要的类型替代模板参数,然后开辟空间,创造出一个实例,然后编译这个实例。如果我们没有调用这个模板类,编译器不会编译它。(按需编译)
2.1. 标准库中的类模板 (Class templates in the standard library)
std::vector
就是一个典型的模板类。
2.2. 切割类模板 (Splitting up class templates)
因为类/函数模板本质上并不一个类/函数,它只是一个模板(stencil),根据我们提供的参数类型去获取对应的正常类/函数。因此,在有些场合使用方法是和普通类/函数不一样的。
比方说,对于正常类,我们的习惯是在头文件里定义类,然后把成员函数的定义放在对应的 C++ 文件中。这样,这个类的源代码就能够像一个独立项目文件那样进行编译。但是这个办法对一个类模板就行不通。
如果我们执意要这么写,就会得到一个 liner error。这是为什么呢?比方说我们有 main.cpp,Array.h 和 Array.cpp。Array.h 定义了一个 Array 的类模板,Array.cpp 中定义了这个类模板中的函数。
因为一个编译器要使用模板,必须同时知道模板定义(不仅仅是模板声明)和模板类型,然后才能实例化。当 Array.h 被 include 在main.cpp 中时,模板类的定义就被拷贝到 main.cpp 中(注意,成员函数这时候只有声明,因为它的定义在 Array.cpp
中)。当编译器在执行 main.cpp 时发现模板类并进行实例化时,它能找到类模板的定义,所以编译没有问题。但是,然后之后当程序需要链接以获得成员函数的定义时,它就需要找到 Array.cpp
并编译去获得成员函数的定义。但是,这时候,编译器已经忘了 main.cpp
里面的参数类型具体是什么,所以,链接失败。报错 linker error。
解决办法:
- 把所有模板类和函数的定义全部放在头文件。(推荐,尽管这有可能会增加编译时间和链接时间,但是因为链接器通常会消除重复定义,所以并不会增加可执行文件的大小)。
- 把
Array.cpp
改成Aarray.inl
(inl 的意思是 inline),最终结果和方法1是一样的。 #include "Array.cpp"
(不推荐,不是标准用法)- 保留
Array.h
和Array.cpp
不动,增加第三个文件templates.cpp
(个人不喜欢)
// Ensure the full Array template definition can be seen
#include "Array.h"
#include "Array.cpp" // we're breaking best practices here, but only in this one place
// #include other .h and .cpp template definitions you need here
template class Array<int>; // Explicitly instantiate template Array<int>
template class Array<double>; // Explicitly instantiate template Array<double>
// instantiate other templates here