C++模板的优缺点是什么?

35

我一直在和朋友们谈论关于C++模板的使用,有些人完全赞成使用它,而另一些人则完全不同意。

其中一些好处包括:

  • 它们更加安全易用(类型安全)。
  • 它们是为API进行泛化的好方法。

你还能告诉我哪些关于C++模板的好处呢?

那么又有哪些关于C++模板的坏处呢?

编辑:我之所以问这个问题是因为我正在备考考试,目前正在学习C++模板的主题。所以我想更多地了解它们。

10个回答

59

模板是一种非常强大的机制,可以简化很多事情。但是要正确使用它们需要大量的时间和经验 - 以决定何时适当地使用它们。

对我来说,最重要的优点是:

  • 减少代码重复(通用容器,算法)
  • 减少高级代码重复(MPL和Fusion)
  • 静态多态性(=性能)和其他编译时计算
  • 基于策略的设计(灵活性,可重用性,更容易进行更改等)
  • 无成本增加安全性(例如通过Boost Units进行维度分析,静态断言,概念检查)
  • 函数式编程(Phoenix),惰性求值,表达式模板(我们可以在C ++中创建特定领域的嵌入式语言,我们拥有出色的Proto库,我们拥有Blitz ++)
  • 日常生活中使用的其他不太引人注目的工具和技巧:
    • STL和算法(forfor_each之间有什么区别)
    • 绑定、lambda(或Phoenix)(编写更清晰的代码,简化事物)
    • 增强功能(使编写回调更容易)
    • 元组(如何通用地哈希元组?例如使用Fusion ...)
    • TBB(parallel_for和其他STL类似的算法和容器)
    • 您能想象没有模板的C ++吗?是的,我可以,在早期的时候由于编译器限制,您无法使用它们。
    • 您会在没有模板的情况下编写C ++吗?不,因为我将失去上述许多优点。
    • 缺点:

      • 编译时间(例如,加入Sprit、Phoenix、MPL和一些Fusion,你可以去喝咖啡了)
      • 能够使用和理解模板的人不那么常见(并且这些人非常有用)
      • 认为他们能够使用和理解模板的人很常见(而这些人很危险,因为他们可能会使您的代码变得混乱。但是,经过一些教育/指导后,他们中的大多数人将加入前面提到的组)
      • 模板export支持(缺乏)
      • 错误信息可能过于复杂(经过一些学习,你可以找到需要的内容,但仍有难度...)

      我强烈推荐以下书籍:


2
您忘记提到生成代码的大小也会增加。这就是为什么在存储容量非常低的嵌入式设备中很少使用模板(除非您将模板定义在源文件内(即仅使其可在该源文件内访问而不可导入源文件头文件的任何其他文件)。 - rbaleksandar
@rbaleksandar 这并不一定是正确的。说模板会增加代码量的人通常没有进行类似对比 - 他们将模板代码与解决问题的其他方案进行了比较,而不是将模板代码与等效的非模板代码进行比较。 - Pharap
@Pharap Java使用类型擦除来减少生成的代码。为什么C++没有这个特性? - Sourav Kannantha B
1
@SouravKannanthaB 话虽如此,在C++中实际上可以进行类型擦除。例如,std::function类型使用类型擦除来允许它引用函数指针和lambda函数。这是类型擦除有用的一个例子,但总的来说,与具体化的泛型或模板相比,类型擦除有很多缺点,这就是为什么大多数语言通常选择更好的方法的原因。 - Pharap
@SouravKannanthaB 我相当确定 C++ 的模板实际上不算作具体化的泛型,因为类型信息在运行时不可用。C# 是使用具体化泛型的语言的一个例子 - 泛型参数的类型在运行时是可用的。这里有一个关于三种不同方法的很好的 SO 答案。Kotlin 是一个针对 JVM 的语言,它使用具体化泛型,但与 C# 的泛型相比,其方法更加昂贵,因为 CLR 对具体化有适当的支持,而 JVM 没有。 - Pharap
显示剩余2条评论

19

从积极的角度来看,C++模板:

  • 允许类型泛化

  • 减少了需要键入的冗余代码量

  • 有助于构建类型安全代码

  • 在编译时进行评估

  • 可以提高性能(作为多态的替代方案)

  • 有助于构建非常强大的库

从消极的角度来看:

  • 如果不小心使用,可能会很快变得复杂。

  • 大多数编译器会给出晦涩的错误消息。

  • 使用/调试高度模板化的代码可能很困难。

  • 至少有一个语法怪癖(>>运算符可能会干扰模板)。

  • 帮助使C++非常难以解析。

总之,应谨慎考虑何时使用模板。


3
现在,值得庆幸的是,">>" 运算符的奇怪行为已经消失了。 - Pablo

9

我的看法比较消极。

C++类型从未被设计成用于在编译时进行计算。使用类型来实现计算目标的概念显然是一种hack,而且这种hack是无意中发现的,而不是有意寻找的。

..

在代码中使用MP的奖励是满足解决难题的瞬间。你用100行代码完成了本应需要200行才能完成的工作。你通过费解的错误消息坚持了下来,最终找到了一个精确的3行模板函数来重载。当然,你的维护者们将不得不投入更多的时间来实现相同的效果。


2
我同意,我发现在日常编码中使用模板的劣势比优势更多,特别是在你提到的第二点上。 - Alexis Pautrot

4
好处:强大; 允许您:
  • 规定编译时属性和计算
  • 描述通用算法和数据结构
  • 完成许多其他工作,否则这些工作将是重复、乏味且容易出错的
  • 使用语言内完成它们,而不用使用宏(宏可能会更危险和深奥!)
坏处:强大; 允许您:
  • 引发冗长、误导和深奥的编译时错误(虽然没有宏那么深奥和误导...)
  • 创建深奥和有害的设计错误(虽然不像宏那么容易出错...)
  • 如果不小心的话会导致代码膨胀(就像宏一样!)
模板大大增加了可行的设计空间,这并不一定是一件坏事,但这确实使得它们更难以好好使用。模板代码需要维护者理解不仅是语言特性,还要理解语言特性的设计后果;从实践角度来说,这意味着许多开发人员组避免使用C++模板除了最简单和最制度化的应用程序。
总的来说,模板使语言变得更加复杂(并且难以正确实现!)。模板并不是有意被设计成图灵完备的,但它们确实是这样——因此,即使它们能够做任何事情,使用它们可能会带来更多麻烦。

3

模板应该谨慎使用。

"难以调试"和"难以阅读"不是反对良好的模板使用和良好抽象的好论点。

更好的负面论点应该指向STL有很多"陷阱",并且将模板用于STL已经涵盖的目的是重复造轮子。 模板还会增加链接时间,这可能对某些项目产生影响,并且在语法上具有许多奇特之处,可能对人们来说很晦涩。

但是通用代码重用、类型特征、反射、智能指针甚至元编程的积极因素往往超过了消极因素。您必须确保模板总是被小心节约地使用。 它们并不是每种情况下最好的解决方案,甚至常常不是第二或第三的最佳解决方案。

您需要拥有足够经验的人来编写模板,他们可以避免所有陷阱,并对模板何时会使事情变得更加复杂有良好的判断。


2
一些人讨厌模板(我也是),因为:
- 从可维护性的角度来看,错误使用模板可能会产生比它们最初节省的时间更强十倍的负面影响。 - 从优化的角度来看,它们允许的编译器优化与最佳算法和多线程的使用相比微不足道。 - 从编译时间的角度来看,错误使用模板可能会对解析、编译和链接阶段产生非常负面的影响,当糟糕的模板声明在每个编译单元中带来大量无用的寄生声明时(这里是如何通过200行代码生成1Mb的.obj文件)。
对我而言,模板就像一个集成了喷火器和手榴弹发射器的链锯。在我生命中的某个时刻,我可能有特定的需要。但大多数时候,我使用普通的锤子和简单的锯子来建造东西,这样做效果也很好。

2
我还没有看到的一个缺点是常规类和类模板实例之间微妙的语义差异。 我可以想到以下几点:
  1. 祖先类型中的typedefed类型名称不会被模板类继承。
  2. 需要在适当的位置添加typename和template关键字。
  3. 成员函数模板不能是虚拟的。
这些问题通常可以克服,但它们很麻烦。

1
优点:可以创建通用数据类型。
缺点:代码膨胀。

在我看来,所有的缺点中,代码膨胀是最不重要的。 - j_random_hacker
3
代码臃肿并不是一个问题。因为这就是你本来必须写的代码。 - Martin York

0

我不明白它们为什么难以阅读。有什么不可读之处吗?

vector <string> names;

举个例子?你会用什么来替换它?


31
您是否是指“vector<basic_string<char, char_traits<char>, allocator<char> >, allocator<basic_string<char, char_traits<char>, allocator<char> > > >”?该内容为“vector<string>”的更详细的类型描述,两者含义相同。 - Thomas
7
如果你喜欢处理这种类型的工作,我这里有几页模板编译器错误消息,你会非常“喜欢”的。 - j_random_hacker
@Thomas,@j_random_hacker 给我们提供另一种(通用、多功能、健壮)的选择。至于阅读错误信息,经过一段时间后,你可以变得非常熟练,并能即时过滤垃圾信息。通过一天的Spirit或Phoenix,你的错误信息阅读技能可以提升10个等级 ;) - Anonymous
STLfilt是我们的好朋友:它简化了错误信息,只保留了相关的内容。 - Luc Hermitte
2
@Thomas. typedef basic_string<char, char_traits<char>, allocator<char> >, allocator<basic_string<char, char_traits<char>, allocator<char> > > string; 一句话概括。 - Ed James
显示剩余3条评论

-2

可重用的代码是使用模板制作的。它的应用根据每个文件的配置进行。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接