打印

C++模板 - 1(函数模板1)

1

C++模板 - 1(函数模板1)

我们先定义一个函数模板,看看长什么样子
复制内容到剪贴板
代码:
template<typename T>
T max(T a, T b) {
    return b < a ? a : b;
}
有个template,它后面的是类型参数列表,在里面可以用typename(class也可以,但是struct可不行)引入一个类型参数,这里叫T,其实叫什么都可以,反正只是一个类型占位符,不过恰当地命名会大大增强代码的可读性,“好的代码本身就是注释”(不过,写代码时命名总是让我头疼的一件事)。

好了,我们成功地引入了一个类型参数,在其后的函数定义中,任何需要类型的地方我们都可以使用这个T了。那这个T到底是什么呢?可以是任何类型,也可以什么都不是(void也是有效的类型哟)。如果你max(3, 4),那T就是int,如果max(0.3, 0.4),那T就是double。这些都不用你操心了,你定义好这个函数群组的样子就可以了,编译器在编译你的代码时会按照你的实际使用需求,生成相应类型版本的函数定义,一个类型生成一个定义,不多不少(感谢勤勤恳恳,任劳任怨的编译器)。如果你在代码中根本没有用到,那编译器也就乐得假装没有看见了。

太好了,我刚刚实现了一个类,Person,来,来,咱俩PK一下,max(my, you)。很不幸,这次你得到的是一大段编译错误,怎么回事?稍微观察一下函数模板的定义,你就会发现,其实这个T是有要求的,不是随便一个类型扔进去,编译器都会欣然接受。首先,这个类型需要定义操作符<,也就是这个类型是可比较大小的;其次,这个类型必须是可复制的,满足函数参数的传递和值的返回。
复制内容到剪贴板
代码:
class Person {
public:
  Person(std::string name, const unsigned int age)
      : name_(std::move(name)), age_(age) {}

  bool operator<(const Person &other) const { return age_ < other.age_; }

private:
  std::string name_;
  unsigned int age_;
};
这次PK成功了。Nice!

最后说一点题外话。

与普通代码不同,对于模板代码,编译器的编译过程通常分为两个阶段。第一阶段仅仅检查模板定义的语法错误,还有那些不依赖于类型参数的代码。如果你并没有实际使用模板的定义,也就是说编译器不需要用某个特定的类型完成模板的实例化,编译器的工作就到此结束了。在具体类型参数实例化时,编译器进入到第二阶段,这时会用特定的类型替换模板的类型参数,对第一阶段遗漏的所有与类型参数相关的代码进行编译。

所以有时你可能先定义了一个函数模板或类模板,但并没有用到它。代码编译运行一切正常,你开开心心地努力工作着。直到有一天你增加了一点点功能,或者修改了一点点,可能只有一行代码,结果编译器无情地扔给你一大段篇幅的编译错误信息。怎么回事?不可能吧?你反复检查刚才的改动N遍,没有问题啊。很有可能是你的这一点点改动触发了编译器的模板实例化,启动了之前未有过的针对某个模板定义的第二阶段编译,而这次的实例化的类型并不满足模板定义的约束。

还有一点需要说明的是,编译器在实例化模板的过程中需要模板的完整定义,原因很简单,编译器要给你生成这个类型版本的完整定义,所以模板通常都是完整地定义在头文件中。

好了,先到这里,希望学习始终是轻松快乐的。
本帖最近评分记录
  • 坏小子很坏 金币 +7 感谢分享,论坛有您更精彩! 2024-6-15 23:01

TOP

0
请问下 c++涉及模版的报错信息感觉一直都非常反人类, 不知道有没有什么工具或者方法可以优化呢?

TOP

0
模板的出错信息可读性非常差,原因很多,我对编译器不是很懂,只能说个大概。模板编译是非常复杂的,曾经有个编译器的作者说过,他在增加一个模板的编译特性时使用的代码超过了之前C++的编译代码之和,编译模板其实是代码生成,这中间涉及到嵌套和递归,同时很多模板类的类型只能靠编译器推断完成,无法手工写出,如果编译出错发生在多层嵌套或深度递归时,你就可以想象出错信息会是什么样子。
C++20实现了 concept,这个可以很好地限定模板参数的类型,同时 static_assert 也可以快速检测出类型约束的违反。模板代码调试本身难度很高,需要慢慢来,先简单一些
本帖最近评分记录

TOP

当前时区 GMT+8, 现在时间是 2025-3-13 06:41