【深度C++】之“类模板”

0. 什么是模板

C++中的模板(template),是泛型编程的基础。一个模板就是一个蓝图,用来创建类或函数的蓝图。

模板主要分为两类:

  1. 函数模板(function template)
  2. 类模板(class template)

本文主要介绍类模板(class template)

关于模板的全部内容,参考【C++深陷】之“模板”

1. 定义类模板

一个例子:

template <typename T>
class Blob {
public:
    // 类型成员
    typedef T value_type;
    typedef typename std::vector<T>::size_type size_type;
    // 构造函数
    Blob();
    // 函数成员,定义在类内
    size_type size() const { return data->size(); }
    void push_back(const T &t) { data->push_back(t); }

    // 函数成员,定义在类外
    T &operator[](size_type i);
private:
    // 数据成员
    std::shared_ptr<std::vector<T>> data;
};

template关键字不可以少,紧接模板参数列表。

在类中可以像使用其他类型一样使用T,我们定义了一个data,是指向vector<T>类型的shared_ptr智能指针。

使用类型别名技术,我们定义了两个类型成员,见第5节。

我们定义了一个构造函数,两个类内成员函数,一个类外成员函数,定义见第2节。

至于为什么size_type类型别名这句话有一个typename,见第8节。

2. 在类外定义函数成员

类内的函数成员直接定义即可,类外的按如下格式:

template <typename T>
Blob<T>::Blob()
    : data(std::make_shared<std::vector<T>>())
{}

template <typename T>
T &Blob<T>::operator[](size_type i) {
    return (*data)[i];
}

必须以template开头,后接和类模板相同模板参数列表

接下来进入了Blob<T>的作用域内,可以适当的省略,见第6节。

3. 类模板的实例化

在实例化类模板时,我们必须显示指定模板实参,即显示模板实参(explicit template argument)

Blob<int> ia;  // 此时T为int类型

上面代码编译之后,编译器重写Blob模板,将每个T都换成int

class Blob {
public:
    // 类型成员
    typedef int value_type;
    typedef typename std::vector<int>::size_type size_type;
    // 构造函数
    Blob();
    // ...

    int &operator[](size_type i);
private:
    // 数据成员
    std::shared_ptr<std::vector<int>> data;
};

注意,上述代码仅仅是示例,并非每个函数成员都会被实例化,见第4节。

4. 成员函数的实例化

默认情况下,一个类模板的成员函数只有当程序用到它时才进行实例化。如果一个成员函数没有被使用,则它不会被实例化。

但是C++总会给你意外或惊喜,此时我们可以记住这个特性,但也要认识到除了默认情况很多情况模板类的成员函数都被实例化了。

5. 类模板的类型别名

类型别名技术,可以通过typedefusing来是我们的程序可读性变强。

类模板也可以:

// 存储string专用的blob
// 此时必须使用具体类型而不能是T
typedef Blob<string> StrBlob;

严格意义上来说,不可以定义Blob<T>typedef,但是可以定义一个模板的类型别名

// twin是一个类型别名
// 但是是一个有模板实参的类型别名
template<typename T> using twin = pair<T, T>;

// typedef pair<T, T> twin
// 是不行的

// 这样使用
twin<int> pos;

6. 省略模板参数列表

在模板类自己的作用域内,可以省略模板参数列表,例如:

template <typename T>
class Blob {
public:
    // ...省略
    // 没有使用Blob<T>
    Blob &operator=(const Blob &);
private:
    // 数据成员
    std::shared_ptr<std::vector<T>> data;
};

上面的赋值运算符没有使用Blob<T>的形式。

在类外:

template <typename T>
Blob<T> &Blob<T>::operator=(const Blob &rhs) {
    // ...
}

在遇到Blob<T>::之后,都属于类的作用域,都可以不添加<T>

7. 类模板中的静态成员

类模板也可以包含静态成员:

template <typename T>
class Foo {
public:
    static std::size_t count() { return ctr; }
private:
    static std::size_t ctr;
};

每个Foo实例都有其自己的static成员实例,Foo<X>Foo<U>互不干扰。

在类外定义静态成员变量需要带上类的模板参数列表:

template <typename T>
size_t Foo<T>::ctr = 0; // 定义并初始化ctr

使用静态成员函数,可以用一下两种方式:

// 方式1
auto ct = Foo<int>::count();

// 方式2
Foo<double> df;
ct = df.count();

8. 如何区分类模板中的类型成员和静态成员

类的成员一共有5项:

  1. 类型成员
  2. 数据成员
  3. 函数成员
  4. 静态成员:静态成员变量、静态成员函数
  5. 成员模板(函数)

其中类型成员静态成员都属于类本身,通过作用域运算符::访问。

当我们定义了一个类模板,有模板参数T。假设T是一个类类型,我们是可以访问T中的类型成员静态成员,此时我们无法区分开访问的是哪一个。比如:

// 是T中的静态变量s 乘 p
// 还是T中的类型成员定义了一个指针p
T::s * p;

C++默认访问的是静态数据,如果写成上面,就执行乘法。

可以通过typename关键字显示指定我访问的是类型:

typename T::s *p = nullptr;

此处的typename和模板参数列表中的typename功能不同,注意区分。

这也是为什么前例中Blobsize_type有一个typename

template <typename T>
class Blob {
public:
    // 类型成员
    typedef T value_type;
    typedef typename std::vector<T>::size_type size_type;
    // ...
};

9. 总结

学会定义类模板、在类外定义类模板的成员函数,以及在类模板作用域内用到类模板名时可以适当省略。

在实例化类模板的时候,必须要指定模板实参。

可以使用类型别名技术,简化我们的定义。

使用typename来区分类模板的静态成员和类型成员。

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值