cpp_6_0_模板 template

  1. Overview
  2. Why Templates? 为什么要引入 Template 模板?
  3. Function Template 函数模板
  4. Class Template 类模板
  5. Generic Programming 泛型编程
  6. Reference

Overview

1.Why Templates? 为什么要引入Template模板?
2.Function Template 函数模板
3.Class Template 类模板
4.Generic Programming 泛型编程

Why Templates? 为什么要引入 Template 模板?

假设我们有一个函数 max(int x, int y), 比较两个 int 类型的 x, y 的值

int max(int x, int y) {
  return x > y ? x : y;
}

但是有时候我们需要用 max 函数的功能, 去返回两个 double 类型的 x 和 y 的值

double max(double x, double y) {
  return x > y ? x : y;
}

所以我们想要一种通用版本的 max 函数写法, 能够支持参数是任何的类型.

intuitively,
1.模板使得函数 (或者类) 支持多个不同的数据类型
2.不同于一般的定义下所有的类型都需要被指定, 使用 template 时, 我们使用1个 (或者多个) placeholder类型
3.类型在定义模板的时候是未知的, 然后用的时候提供具体的类型
4.一旦 template 被定义, 编译器使用 template 去生成很多需要的重载函数 (如果是模板类的话就相应生成多个重载类), 其中每个重载函数 (或者类) 采用不同的类型
5.字典里面模板这个词的解释:

a template is a model that serves as a pattern for creating similar objects. One type of template that is very easy to understand is that of a stencil.

6.C++ 标准库里面使用了大量的模板, 典型的例子是 standard template library (STL, 标准模板库)

Function Template 函数模板

1.函数模板是一种作用在函数上的通用定义, 函数模板用于生成一个或者多个重载函数, 每个函数具有一组不同的实际类型.
2.当我们创建函数模板的时候, 我们用 placehoder type (又叫做 template parameters 或者informally template types ) 来表示任何参数类型, 表示返回类型, 或者表示希望稍后指定的函数体中使用的类型; 其实可以理解为相比int类型/double类型等各种类型, 新定义了一种特殊的类型, 叫做”模板参数”类型

// 用于比较两个数的大小的函数模板
template <typename T>  // 模板参数
T max(T x, T y) {
  return (x < y) ? y : x;
}

3.函数模板实例化 (function template instantiation)

当编译器遇到函数

max<int>(1, 2)

的时候, 编译器发现函数定义

max<int>(int, int)

并不存在, 因此编译器要使用我们的函数模板

max<T>

去实例化一个函数, 这个过程叫做函数模板实例化 function template instantiation, 被实例化出来的函数叫做函数实例 function instance; 这种因为函数调用而触发的过程叫做 implicit instantiation

4.当第一次实例化完成后, 后续继续调用不需要同类型的函数实例

#include <iostream>

template <typename T>
T max(T x, T y) {
  return (a < b) ? b : a;
}

int main () {
  cout << std::max<int>(1, 2) << endl; // 实例化, 并调用函数max<int>(int, int)
  cout << std::max<int>(4, 3) << endl; // 调用已经实例化的函数 function<int>(int, int)
  cout << std::max<double>(1.1, 2.2) << endl; // 实例化, 调用函数max<double>(double, double)
  return 0;
}

4.模板参数推导 template argument deduction
当我们想用 int 类型的 max 函数, 通过指定去告诉编译器实例化出来类型的函数; 另外, 我们利用模板函数推导 template argument deduction 去让编译器去推导出来类型

std::cout << max<int>(1, 2) << endl; // 指定我们想要调用max<int>

我们让编译器去帮忙推导类型

std::cout << max<>(1, 2) << endl; //  the compiler will only consider max<int> template function overloads when determining which overloaded function to call.
std::cout << max(1, 2) << endl; // the compiler will consider both max<int> template function overloads and max non-template function overloads.
#include <iostream>

template <typename T>
T max(T x, T y) {
  std::cout << "called max<int>(int, int)\n";
  return (x < y) ? y : x;
}

int max(int x, int y) {
  std::cout << "called max(int, int)\n";
  return (x < y) ? y : x;
}

int main() {
  std::cout << max<int>(1, 2) << '\n'; // selects max<int>(int, int)
  std::cout << max<>(1, 2) << '\n';    // deduces max<int>(int, int) (non-template functions not considered)
  std::cout << max(1, 2) << '\n';      // calls function max(int, int)
  return 0;
}

输出结果

called max<int>(int, int)
2
called max<int>(int, int)
2
called max(int, int)
2

5.带有非模板参数的函数模板 function templates with non-template parameters
函数模板可以同时有模板参数类型, 也可以有非模板参数类型, 两个可以混着用

template <typename T>
int func(T x, double y) {
  return 5;
}

5.实例化的函数不一定被正确编译
比如string类和int类是没办法完成加法的操作的

#include <iostream>
#include <string>

template <typename T>
T addOne(T x) {
  return x + 1;
}

int main() {
  std::string hello("Hello, world!");
  std::cout << addOne(hello) << '\n';
  return 0;
}

6.在多个文件中使用模板 using function templates in multiple files
将所有的模板代码都放在.h文件中, 而不放在.cc文件中; 任何需要访问模板的文件都可以#include 相关的头文件,模板定义将由预处理器复制到源文件中, 然后编译器将能够实例化任何需要的函数
add.h

#ifndef ADD_H
#define ADD_H

template <typename T>
T addOne(T x) {// function template definition
  return x + 1;
}

#endif

main.cpp

#include "add.h" // import the function template definition
#include <iostream>

int main() {
  std::cout << addOne(1) << '\n';
  std::cout << addOne(2.3) << '\n';
  return 0;
}

7.带有多个模板类型的函数模板
函数模板中, 我们需要用到不止一种类型, 比如同时需要 doule 和 int, 所以可以定义两个模板类型参数; 这种情况下, 参数返回值的类型就需要格外注意了, 因为用到了不止一种类型; c++14 里面一种比较简单的方法是用 auto 类型做返回类型

#include <iostream>

template <typename T, typename U>
T max(T x, U y) {
  return (x < y) ? y : x;
}

int main() {
  std::cout << max(2, 3.5) << '\n';
  return 0;
}

Class Template 类模板

我们可以定义类的模板, 其实就是函数模板推广到 oop 的

#include <iostream>
#include <vector>

template <class T>
class Stack {
 private:
  vector<T> elems;
 public:
  T top() const;
  void push(T const&);
  void pop();
  bool empty() const {
    return elems.empty();
  };
}

template <class T>
void Stack::push(T const& elem) {
  elems.push_back(elem);
}

Generic Programming 泛型编程

1.模板类型有时候叫做泛型类型 (generic types) , 用模板编程又叫做泛型编程 (Generic Programming, GP).
2.虽然 cpp 通常非常关注类型和类型检查, 但是泛型编程让我们能够更关注算法和数据结构, 而不去多关注类型信息.
3.模板的优势: 习惯写函数模板有助于大量减少需要维护的代码量
4.模板的缺点:
(i). 函数模板实例化会扩展成大量的代码, 使得编译会很慢. They can expand into a crazy amount of code, which can lead to code bloat and slow compile times;
(ii). 可能造成很多未知的问题. They tend to produce crazy-looking, borderline unreadable error messages that are much harder to decipher than those of regular functions.
5.整体而言, 使用模板的缺点相比使用模板优势还是很小的;
6.使用模板的基础经验是: 首先创建普通函数, 然后发现需要对不同类型进行重载, 则将他们转化成函数模板

Reference

  1. https://www.learncpp.com/cpp-tutorial/function-templates/

转载请注明来源, from goldandrabbit.github.io

💰

×

Help us with donation