Skip to content

C++语言导学(8): 模板

引言

模板是一个很复杂的主题,这里只涉及最基础的部分。

模板(template)是一个类或一个函数,可以用一组类型或值来进行参数化。

模板是一种编译时机制,一个模板加上一组模板实参被称为特例化,会由编译器根据实参自动生成对应的代码进而编译。

模板参数:类型

比如:

tempalte<typename T>
class Vector{
    private:
        T* elem;
        int sz;
    public:
        explicit Vector(int s);
        ~Vector(){ delete[] elem;}
        //...
        T& operator[](int i);
        const T& operator[](int i) const;
        int size(){ return sz; }
};

template<typename T>
Vector<T>::Vector(int s){
    if(s < 0)
        throw Negative_size();
    elem = new T[s];
    sz = s;
}

//...

这里的typename T就是指的模板类型参数T。

可以给自定义类型定义begin()和end()函数以支持范围for循环:

template<typename T>
T* begin(Vector<T>& x)
{
    return x.size() ? &x[0] : nullptr;
}

template<typename T>
T* end(Vector<T>& x)
{
    retur x.size() ? &x[0]+x.size() : nullptr;
}

void f(Vector<string>& s)
{
    for(auto &x: s)
    {
        cout << s << endl;
    }
}

另外,一旦使用模板,则声明和实现要放在一起,不能分开放到头文件和实现文件中,不支持分离编译。

模板参数:值

template<typenaem T, int N>
struct Buffer{
    using value_type = T;
    constexpr int size(){ return N; }
     //...
};

Buffer<int, 10> buf;
Buffer<char, 1024> buf;

Buffer<int, 10>::value_type x; // 等同: int x
Buffer<char, 1024>::value_type ch; // 等同: char ch;

这里的int N则是模板值参数,模板值参数必须是常量表达式。

这里用到using是模板的别名功能,用于简化符号,写出更通用、抽象的模板,建议为自己的模板定义value_type

参数化:函数模板

template<typename Sequence, typename Value>
Value sum(const Sequence&s, Value v)
{
    for(auto x: s)
    {
        v += x;
    }

    return v;
}

vector<int> vi{1,2,3,4,5};
int x = sum(vi, 0); // 15

函数模板可以作用于类的成员函数,但不能是virutal成员函数,因为virutal是运行时概念,而模板是编译时概念。

参数化:函数对象

模板用于函数对象(Function Object, functor):

template<typename T>
class Less_than{
    const T val;
public:
    Less_than(const T& v): val(v){}
    bool operator(){cosnt T&x) const { return x < val; }
};

Less_than lti{42};
Less_than lts1{"Hello"s}; // 模板自动推断是string
Less_than<string> lts2{"Hello"}; // 不明确指定string则会推断为const char *

函数对象非常适合作为算法的参数。

参数化:lambda表达式

带有auto参数的lambda成为了一个模板,又称泛型lambda:

template<typename S>
void rotate_add_draw(vector<S>& v, int r)
{
    for_all(v, [](auto& s){ s->rotate(r); s->draw(r);});
}

vector<unique_ptr<Shape>> v1;
vector<Shape*> v2;

rotate_and_draw(v1, 45);
rotate_and_draw(v2, 90);

通过auto来自动推断,从而达到模板参数化的效果。

总结

  • 使用模块以提高代码的抽象水平
  • 如果只需要简单的函数对象,则首选lambda表达式代替
  • 不能将虚函数成员使用模板来定义
  • 利用模板别名简化符号以隐藏实现细节
  • 模板不可以分离编译,不能头文件和实现文件分开定义。