详解C++ 模板编程

 更新时间:2020年9月8日 20:43  点击:1960

类型模板

类型模板包括函数模板和类模板,基本上是C++开发人员接触模板编程的起点。

下面代码演示了函数模板和类模板的使用方法:

// 函数模板
template<typename T>
T add(const T& a, const T& b) {
 return a + b;
}

// 类模板
template<typename T>
class Point {
private:
 T x[3];
 ...
};

类型模板以template开始声明,尖括号内的typename关键字可用class替代。类型模板中typenameclass具有相同含义,均表示参数类型。实践中typename语义更广泛,表示其后续的参数T是一个类型,不限定于类,建议使用。类型参数T可换成其他任意有意义的合法变量。

C++14新增变量模板:

// 变量模板
template<tyepename T>
constexpr T pi = T(3.1415926535897932385L);

尖括号之于模板犹如小括号之于函数:函数通过小括号()定义和调用,模板使用尖括号<>定义(需template关键字声明)和实例化。上面演示了类型模板定义,下面代码介绍模板实例化:

int a = 1, b = 2;
// 实例化函数模板
std::cout << "add result:" << add<int>(a, b) << std::endl;

// 实例化类模板
auto p = Point<int>();

double radius = .5;
// 实例化变量模板
auto area = pi<double> * radius * radius;

同函数一样,模板可以有默认值:

// 默认类型为int
template<typename T=int>
T add(const T& a, const T& b) {
 return a + b;
}

// 默认类型为double
template<typename T=double>
class Point {
private:
 T x[3];
 ...
};

与函数不同,对于函数模板,如果能从参数推断出模板类型,则可略去尖括号模板实例化参数:

int a = 1, b = 2;
// 合法调用,编译器能根据a b推断出参数类型
std::cout << "add result:" << add(a, b) << std::endl;
// 等同于
std::cout << "add result:" << add<int>(a, b) << std::endl;

然而对于类模板,即使有默认参数,也不能省略尖括号(但是可以省去参数):

template<typename T=double>
struct Point {
 T x[3];
};

// 合法声明
auto p = Point<double>();
// 合法声明,类型使用默认的double
auto p2 = Point<>();
// 非法声明,缺少模板调用标志尖括号
auto p3 = Point();

类型参数模板在实际中使用最多,STL库中vector、map等容器、algorithm中的许多算法都用到了模板。

非类型参数模板

另一类常用模板是非参数模板,用来替代某个具体的值。例如:

// N维空间向量
template<int N>
struct Vector {
 double x[N];
};

// 实例化
auto v = Vector<100>();
...其他操作

需要注意的是,非类型参数模板能使用的类型十分有限,只有(signed/unsigned)整数、char和枚举这几种类型可用(参考switch语法)。

同类型模板一样,非类型参数模板也可以有默认值,但应用到类模板实例化也不能省略尖括号。

类型模板和非类型参数模板可以结合一起用:

template<typename T, int N>
struct Point {
 T x[N];
};

类型模板解决了类型问题,非类型参数模板解决了值的问题,实际中应用也十分广泛。作为递归的经典场景,斐波那契数列可以用非类型模板解决:

template<int N>
struct Fib {
 static constexpr int value = Fib<N-1>::value + Fib<N-2>::value;
};
// 模板特化
template<>
struct Fib<1> {
 static constexpr int value = 1;
};
// 模板特化
template<>
struct Fib<0> {
 static constexpr int value = 0;
};

// 调用
std::cout << "Fib(10): " << Fib<10>::value << std::endl;

这个例子出现了”模板特化”,接下来介绍。

模板特化/偏特化

定义模板后,希望在特定条件下使用单独的模板,这便是模板特化。上文中斐波那契数列定义的template<int N> struct Fib是母模板,接下来又定义了0和1两个特化模板(子模板),指示编译器遇到Fib<0>和Fib<1>的情况,使用这两组单独定义。需要注意的是特化模板的template参数为空,具体模板参数放到了模板名称处,类似于模板实例化。

对多个模板参数的情形,如果只特化某个模板参数,便是偏特化。例如:

// 泛型模板定义
template<typename T1, typename T2> struct Add; 
// 特化模板
template<> struct Add<int, int> {...};
// 偏特化模板
template<typename T> struct Add<T, long> {....};

模板特化/偏特化类似于函数重载,能针对特殊情况进行特别处理。

模板匹配与SFINAE

模板特化使得同一个模板名称有了多个定义,代码具体调用时会遇到模板匹配问题。理解模板匹配机制的关键便是SFINAE,这也是进阶模板编程的必备知识点。

SFINAE是Substitution failure is not an error的缩写,翻译过来便是:匹配(替换)失败不是错误。

怎么理解这句话呢?

对于上面的斐波那契数列数列代码,编译器遇到Fib<10>::value的代码,(可能)先会尝试匹配Fib<0>,发现匹配不上,这是一个Substitution failure,但不是error,所以编译器继续尝试其他可能性。接着匹配Fib<1>,同样发现匹配不上,忽略这个Substitution failure继续尝试Fib<N>,OK,这一次没问题,编译成功。

如果是Fib<-1>::value,编译器达到最大递归深度也找不到一个合适的匹配模板,这是一个error,因此编译失败。

备注:理解上面的话需要对编译过程稍加了解,编译过程会输出许多信息,编译器一般只有遇到error才会终止编译,比较常见的warning则不会。模板匹配中的Substitution可能连warning都算不上,不会影响编译器继续尝试匹配

理解SFINAE是看懂稍微深奥点模板代码的基本功,重点便是:不怕你模板多,就怕找不到合适的模板。

两阶段编译

有了模板(元)编程,C++源码编译可以分为前期和后期,构成两阶段编译。前期是模板的天下,编译器扫描模板实例化语句,生成运算结果和具体代码;后期编译器介入,再编译生成机器码。

模板代码运行在编译期,因此有如下特点:

  • 没有实例化的模板代码,即使有语法错误,编译器也不会检查和报错。对按代码行数考核KPI的C++码农,这绝对是福音,新增template代码十万行,瞎编乱写都可以,只要不实例化,永远能编译通过,编译后的文件大小(一般)不变,也不影响现有代码运行;
  • 对于常量,编译前期直接计算,没有运行时开销。上文中的斐波那契数列值在编译期便已经计算出来了;
  • 无法运行期动态调用代码。例如下面的要求做不到:

template<int N>
struct Point {double x[N];};
// 根据输入动态生成类,无法实现和编译成功
int n;
std::cin >> n;
auto p = new Point<n>();

  • 模板和多态/虚函数(理念)冲突。多态/虚函数的关键是运行期动态调用代码,而模板在编译期确定,因此两者理念上是冲突的。所以,如果你想一个成员函数既是模板函数,又是虚函数,怎么做实现预期?

C/C++编译有个预处理过程,只是做简单字符串替换,没有具体运算,与模板生成代码不同

在编译前期,除了模板代码被解释执行,其他代码信息都在,因此模板代码拥有类似反射/自省的能力,这也是C++元编程功能强大的原因之一。

C++11中的变化

C++11带来了许多新特性和重大更新,可以认为C++11是一门新的语言。就模板来说,主要更新点如下:

1. 可以使用static constexpr int代替早期模板代码中的enum。网上许多斐波那契数列代码都是基于早期C++,一律使用enum方式定义字段;

2. 可以使用using代替typedef。这是using语句能力的重大更新,早期我们定义类型或者别名都需要typedef,自C++11开始,简单使用using就可以达到相同效果。

3. C++14引入了变量模板,上文已介绍。

模板优缺点

上文根据自己理解和实践简要介绍了C++模板编程的相关概念,本节总结一下C++模板的优缺点:

C++模板编程优点:

  • 减少代码输入,提高代码重用和编程效率;
  • 支持鸭子类型(Duck typing)的特性,使用便利,功能强大;
  • 某些情况下能减少运行期开销;
  • 能实现元编程,C++高手必备之路;

C++模板编程缺点:

  • 语法看起来是hack黑科技,代码可读性差,编写繁琐;
  • 模板代码调试困难,生成的错误信息也晦涩难懂。你可以还记得刚开始使用STL模板的map等数据类型报错的恐怖提示?
  • 编译时间增加。

感谢阅读,欢迎指正!

以上就是详解C++ 模板编程的详细内容,更多关于C++ 模板编程的资料请关注猪先飞其它相关文章!

[!--infotagslink--]

相关文章

  • C++ STL标准库std::vector的使用详解

    vector是表示可以改变大小的数组的序列容器,本文主要介绍了C++STL标准库std::vector的使用详解,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2022-03-06
  • C++中取余运算的实现

    这篇文章主要介绍了C++中取余运算的实现,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 详解C++ string常用截取字符串方法

    这篇文章主要介绍了C++ string常用截取字符串方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • C++调用C#的DLL程序实现方法

    本文通过例子,讲述了C++调用C#的DLL程序的方法,作出了以下总结,下面就让我们一起来学习吧。...2020-06-25
  • C++中四种加密算法之AES源代码

    本篇文章主要介绍了C++中四种加密算法之AES源代码,小编觉得挺不错的,现在分享给大家,也给大家做个参考。...2020-04-25
  • C++ 整数拆分方法详解

    整数拆分,指把一个整数分解成若干个整数的和。本文重点给大家介绍C++ 整数拆分方法详解,非常不错,感兴趣的朋友一起学习吧...2020-04-25
  • C++中 Sort函数详细解析

    这篇文章主要介绍了C++中Sort函数详细解析,sort函数是algorithm库下的一个函数,sort函数是不稳定的,即大小相同的元素在排序后相对顺序可能发生改变...2022-08-18
  • C++万能库头文件在vs中的安装步骤(图文)

    这篇文章主要介绍了C++万能库头文件在vs中的安装步骤(图文),文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2021-02-23
  • 深入解析WordPress中加载模板的get_template_part函数

    这篇文章主要介绍了WordPress中加载模板的get_template_part函数,其中重点讲解了其函数钩子的使用,需要的朋友可以参考下...2016-01-14
  • 详解C++ bitset用法

    这篇文章主要介绍了C++ bitset用法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • mysql配置模板(my-*.cnf)参数详细说明

    mysql安装成功后有几个默认的配置模板,列表如下: my-huge.cnf : 用于高端产品服务器,包括1到2GB RAM,主要运行mysql my-innodb-heavy-4G.ini : 用于只有innodb的安装,最多有4GB RAM,支持大的查询和低流量 my-large.cnf : 用于...2015-03-15
  • 浅谈C++中的string 类型占几个字节

    本篇文章小编并不是为大家讲解string类型的用法,而是讲解我个人比较好奇的问题,就是string 类型占几个字节...2020-04-25
  • c# socket网络编程接收发送数据示例代码

    这篇文章主要介绍了c# socket网络编程,server端接收,client端发送数据,大家参考使用吧...2020-06-25
  • C++ Eigen库计算矩阵特征值及特征向量

    这篇文章主要为大家详细介绍了C++ Eigen库计算矩阵特征值及特征向量,具有一定的参考价值,感兴趣的小伙伴们可以参考一下...2020-04-25
  • 浅谈node.js中async异步编程

    1.什么是异步编程? 异步编程是指由于异步I/O等因素,无法同步获得执行结果时, 在回调函数中进行下一步操作的代码编写风格,常见的如setTimeout函数、ajax请求等等。示例: for (var i = 1; i <= 3; i++) {setTimeout(functi...2015-10-23
  • VSCode C++多文件编译的简单使用方法

    这篇文章主要介绍了VSCode C++多文件编译的简单使用方法,本文给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友可以参考下...2021-03-29
  • C++ pair的用法实例详解

    这篇文章主要介绍了C++ pair的用法实例详解的相关资料,需要的朋友可以参考下...2020-04-25
  • C++中的循环引用

    虽然C++11引入了智能指针的,但是开发人员在与内存的斗争问题上并没有解放,如果我门实用不当仍然有内存泄漏问题,其中智能指针的循环引用缺陷是最大的问题。下面通过实例代码给大家介绍c++中的循环引用,一起看看吧...2020-04-25
  • C++随机点名生成器实例代码(老师们的福音!)

    这篇文章主要给大家介绍了关于C++随机点名生成器的相关资料,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧...2020-04-25
  • Smarty模板学习笔记之Smarty简介

    1、简介Smarty是一个使用PHP写出来的模板PHP模板引擎,是目前业界最著名的PHP模板引擎之一。它分离了逻辑代码和外在的内容,提供了一种易于管理和使用的方法,用来将原本与HTML代码混杂在一起PHP代码逻辑分离。简单的讲,目...2014-05-31