返回完整目录

1.5 重载函数模板 Overloading Function Templates

和普通函数一样,函数模板也可以被重载,也就是说,同样的函数名可以有不同的函数定义。所以当一个名字被用作函数调用时,编译器必须确定从不同的候选者中决定调用哪一个。这个决策过程可以相当复杂,即使在没有模板的情况下。本小节讨论当包含模板时的重载解析规则。如果读者对没有模板时的函数重载基本规则不熟悉,请查阅附录C,那提供了关于重载解析规则(overload resolution rule)的详略得当的描述。

以下简短的程序刻画了重载函数模板的情形:

  1. // basics/max2.cpp
  2. //两个int类型值的最大值:
  3. int max(int a, int b)
  4. {
  5. return b < a ? a : b;
  6. }
  7. // 两个任何类型值的最大值:
  8. template <typename T>
  9. T max(T a, T b)
  10. {
  11. return b < a ? a : b;
  12. }
  13. int main()
  14. {
  15. ::max(7, 42); //调用两个int值的非模板函数
  16. ::max(7.0, 42.0); //调用max<double>,由模板参数推断而来
  17. ::max('a', 'b'); //调用max<char>,由模板参数推断而来
  18. ::max<>(7, 42); //调用max<int>,由模板参数推断而来
  19. ::max<double>(7, 42); //调用max<double>,没有模板参数推断
  20. ::max('a', 42.7); //调用两个int值的非模板函数
  21. }

该例子显示,非模板函数可以与同名的函数模板共存,也可以用相同的类型进行实例化。在其他因素都相同的情况下,相比于模板生成的版本,重载解析程序(overload resolution process)更倾向于使用非模板版本。第一个调用便落入了这条规则:

  1. ::max(7, 42); //两个int值类型完美匹配非模板函数

如果模板生成一个更匹配的函数,就会选择模板,这由第二个和第三个max()的调用佐证:

  1. ::max(7.0, 42.0); //调用max<double>,由模板参数推断而来
  2. ::max('a', 'b'); //调用max<char>,由模板参数推断而来

此处,模板更匹配因为不需要从double或char转换成int(见附录C.2节参考更多重载解析规则)。

可以显示指定空模板实参列表。该语法意味着只有模板能够解析一次函数调用,但是所有的模板参数应该能从调用实参中推断而得:

  1. ::max<>(7, 42); //调用max<int>,由模板参数推断而来

虽然模板类型推导不可以进行自动类型转换,但普通函数可以进行类型转换,因此最后一个调用使用非模板函数('a'和42.7均可以转化为int):

  1. ::max('a', 42.7); //只有非模板函数允许类型转换

一个有趣的例子是重载求最大值的模板函数,并可以仅仅显式指定返回值类型:

  1. //basics/maxdefault4.hpp
  2. template<typename T1, typename T2>
  3. auto max(T1 a, T2 b)
  4. {
  5. return b < a ? a : b;
  6. }
  7. template<typename RT, typename T1, typename T2>
  8. RT max(T1 a, T2 b)
  9. {
  10. return b < a a : b;
  11. }

如果以如下方式调用max()

  1. auto a = ::max(4, 7.2); //使用第一个模板
  2. auto b = ::max<long double>(7.2, 4); //使用第二个模板

但是,当调用

  1. auto c = ::max<int>(4, 7.2); //ERROR: 两个模板都匹配

两个模板均匹配,这意味着重载解析程序不会倾向于任何一个,所以导致歧义错误(ambiguity error)。因此,当重载函数模板时,应当确保任何调用时仅有一个匹配。

一个常见的情形是同时重载指针和C风格字符串(C-string)的最大值模板:

  1. //basics/max3val.cpp
  2. #include <cstring>
  3. #include <string>
  4. // 两个任意类型值的最大值
  5. template <typename T>
  6. T max(T a, T b)
  7. {
  8. return b < a ? a : b;
  9. }
  10. //最大值的指针
  11. template <typename T>
  12. T* max(T* a, T* b)
  13. {
  14. return *b < *a ? a : b;
  15. }
  16. //C风格字符串的最大值
  17. char const* max(char const* a, char const* b)
  18. {
  19. return std::strcmp(b, a) < 0 ? a : b;
  20. }
  21. int main()
  22. {
  23. int a = 7;
  24. int b = 42;
  25. auto m1 = ::max(a, b); //两个int类型值的max()
  26. std::string s1 = "hey";
  27. std::string s2 = "you";
  28. auto m2 = ::max(s1, s2); //两个std::string类型值的max()
  29. int* p1 = &b;
  30. int *p2 = &a;
  31. auto m3 = ::max(p1, p2); //两个指针的max()
  32. char const* x = "hello";
  33. char const* y = "world";
  34. auto m4 = ::max(x, y); //两个C风格字符串的max()
  35. }

这些max()的重载版本均以值进行传递。重载函数模板时,遵循除非有必要否则不进行改变的准则是个好主意,应当限定参数数量的改变或者显式指定模板参数,否则会产生意想不到的效果。比如,如果实现max()模板时,以引用方式进行参数传递,并且重载以值传递两个C风格字符串,则不能使用3个参数的版本来计算三个C风格字符串的最大值:

  1. // basics/max3ref.cpp
  2. #include <cstring>
  3. //两个任何类型值的最大值(以引用传递方式调用)
  4. template <typename T>
  5. T const& max(T const& a, T const& b)
  6. {
  7. return b < a ? a : b;
  8. }
  9. //两个C风格字符串的最大值(以值传递方式调用)
  10. char const* max(char const* a, char const* b)
  11. {
  12. return std::strcmp(b, a) < 0 ? a : b;
  13. }
  14. //三个任何类型值的最大值(以引用传递方式调用)
  15. template <typename T>
  16. T const& max(T const& a, T const& b, T const& c)
  17. {
  18. return max(max(a,b), c); //如果max(a,b)以值传递方式调用将产生错误
  19. }
  20. int main()
  21. {
  22. auto m1 = ::max(7, 42, 68); //正确
  23. char const* s1 = "frederic";
  24. char const* s2 = "anica";
  25. char const* s3 = "lucas";
  26. auto m2 = ::max(s1, s2, s3); //运行时错误,未定义的行为(undefined behavior)
  27. }

该问题在于调用三个C风格字符串的max(),该语句为:

  1. return max(max(a,b), c);

变为一个运行时错误。因为对于C风格字符串,max(a,b)创建了一个新的临时的局部变量并以引用方式返回,但该临时变量在返回语句完成时便终结了(expire),在主函数中留下一个悬空引用(dangling reference)。不幸的是,该错误相当微妙,在任何情况下可能无法清楚显示(may not manifest itself in all cases)[1]

相反,main()函数中的第一个max()调用不会遇到该问题,虽然为调用实参创建了临时量(7,42,68),但这些临时量在main()函数中创建并持续到语句结束。

这仅仅是一个其行为与预期不一致的示例代码,这是由复杂的重载解析规则引起的。此外,确保一个函数的所有重载版本在调用前被声明,这是由于当一个函数被调用时不是所有对应的重载函数均可见,这一点非常重要。比如,定义一个3个参数版本的max(),当针对int类型的特定的2参数版本不可见时,2参数的模板将被3参数的版本调用:

  1. // basics/max4.cpp
  2. #include <iostream>
  3. // 两个任意类型值的最大值
  4. template <typename T>
  5. T max(T a, T b)
  6. {
  7. std::cout << "max<T>() \n";
  8. return b < a ? a : b;
  9. }
  10. // 三个任意类型值的最大值
  11. template <typename T>
  12. T max(T a, T b, T c)
  13. {
  14. return max(max(a,b), c); //使用int类型的模板版本,因为后面的声明太迟了
  15. }
  16. // 两个int值的最大值
  17. int max(int a, int b)
  18. {
  19. std::cout << "max(int, int) \n";
  20. return b < a ? a : b;
  21. }
  22. int main()
  23. {
  24. ::max(47, 11, 33); //OOPS: 使用max<T>(),而不是max(int, int);
  25. }

细节将在第13.2节中讨论。

脚注


  1. 通常来说,一个合格的编译器(a conforming compiler)甚至不被允许拒绝此代码。

C++ Templates (1.5 重载函数模板 Overloading Function Templates)的更多相关文章

  1. 读书笔记 effective c++ Item 45 使用成员函数模板来接受“所有兼容类型”

    智能指针的行为像是指针,但是没有提供加的功能.例如,Item 13中解释了如何使用标准auto_ptr和tr1::shared_ptr指针在正确的时间自动删除堆上的资源.STL容器中的迭代器基本上都是 ...

  2. c++学习笔记之函数重载和模板理解

    1.函数重载: C++ 不允许变量重名,但是允许多个函数取相同的名字,只要参数表不同即可,这叫作函数的重载(其英文是 overload).重载就是装载多种东西的意思,即同一个事物能完成不同功能. 所谓 ...

  3. 不可或缺 Windows Native (16) - C++: 函数重载, 缺省参数, 内联函数, 函数模板

    [源码下载] 不可或缺 Windows Native (16) - C++: 函数重载, 缺省参数, 内联函数, 函数模板 作者:webabcd 介绍不可或缺 Windows Native 之 C++ ...

  4. [C++] 用Xcode来写C++程序[5] 函数的重载与模板

    用Xcode来写C++程序[5] 函数的重载与模板 此节包括函数重载,隐式函数重载,函数模板,带参数函数模板 函数的重载 #include <iostream> using namespa ...

  5. C++函数模板

    函数模板提供了一种函数行为,该函数行为可以用多种不同的类型进行调用,也就是说,函数模板代表一个函数家族,这些函数的元素是未定的,在使用的时候被参数化. 本文地址:http://www.cnblogs. ...

  6. [c++][语言语法]函数模板和模板函数 及参数类型的运行时判断

    参考:http://blog.csdn.net/beyondhaven/article/details/4204345 参考:http://blog.csdn.net/joeblackzqq/arti ...

  7. C++学习33 函数模板

    在<C++函数重载>一节中,为了求三个数的最大值,我们通过函数重载定义了三个名字相同.参数列表不同的函数,如下所示: //求三个整数的最大值 int max(int a, int b, i ...

  8. 25.C++- 泛型编程之函数模板(详解)

    本章学习: 1)初探函数模板 2)深入理解函数模板 3)多参函数模板 4)重载函数和函数模板 当我们想写个Swap()交换函数时,通常这样写: void Swap(int& a, int&am ...

  9. C++_进阶之函数模板_类模板

     C++_进阶之函数模板_类模板 第一部分 前言 c++提供了函数模板(function template.)所谓函数模板,实际上是建立一个通用函数,其函数类型和形参类型不具体制定,用一个虚拟的类型来 ...

随机推荐

  1. javascrip jason

    JavaScript JSONJSON 是用于存储和传输数据的格式. JSON 通常用于服务端向网页传递数据 . <html><head><meta http-equiv ...

  2. 日志分析-利用grep,awk等文本处理工具完成(2019-4-9)

    0x00 基础日志分析命令 1. tail - 监控末尾日志的变化 $tail -n 10 error2019.log #显示最后10行日志内容 $tail -n +5 nginx2019.log # ...

  3. R 基础绘图体系-基础篇

    1.高水平绘图函数 生成数据 #模拟100位同学学号及三科成绩 num = seq(12340001,12340100) # 形成学号 x1 = round(runif(100,min = 80,ma ...

  4. IDEA中项目的两种打包方式

    本文主要介绍在IDEA中怎么打包,及可以用哪种方式打包. 若是有指正或补充的,欢迎留言~  ٩(●̮̃•)۶ 接下来进入正题: IDEA中打包需要先进行配置,so,我们先打开<abbr titl ...

  5. log4j2.xml配置使用

    jar包: log4j-api-2.10.0.jar log4j-core-2.10.10.jar log4j-1.2-api-2.10.0.jar log4j-slf4j-impl-2.10.10. ...

  6. js原声代码 轮播图

    js轮播图 html部分:建立div,内嵌img标签,可以设置大小, <!doctype html> <html> <head> <meta charset= ...

  7. ken桑带你读源码 之scrapy

    开篇声明 文章讲解源码不一定从入口开始   主题更注重 思路讲解以及核心函数   ok?  废话到此为止 /scrapy/downloadermiddlewares/  文件夹下是下载器的 中间件  ...

  8. 使用 expect 重启失败的 git pull/push 操作

    问题的提出 最近使用 github 上传.下载项目代码时,经常会卡很久,有时候在命令行打了 git push 然后就去上厕所了,结果等我回来的时候,发现 push 早已经失败了,还得重新提交一下.如果 ...

  9. jieba.lcut方法

    jieba库的作用就是对中文文章进行分词,提取中文文章中的词语 cut(字符串, cut_all,HMM) 字符串是要进行分词的字符串对象 cut_all参数为真表示采用全模式分词,为假表示采用精确模 ...

  10. @程序员,如何进入BAT这类一线公司?做到这几点的就有机会!

    跟大家聊一聊很多很多很多人问我的一个问题:中小公司的Java工程师该如何规划准备,才能跳槽进入BAT这类一线互联网公司? 作者简介:中华石杉,十余年BAT架构经验,倾囊相授 我用了三个 “很多” 来形 ...