【问题标题】:What are the disadvantages of using templates?使用模板有什么缺点?
【发布时间】:2011-02-13 16:00:57
【问题描述】:

一些缺点是

  1. 它的语法很复杂
  2. 编译器生成额外代码

【问题讨论】:

  • 编译器生成额外的代码,我在某处(我认为是 Stroustrup 先生)读到现代编译器实际上没有——没有单条指令开销
  • 我不赞成“复杂语法”的说法。如果您发现模板语法令人困惑,则应该专注于继续学习该语言,而不是挑剔它。并且 2 应该在很大程度上无关紧要,IIRC。
  • 我认为额外的代码会发生,例如当您针对 N 个不同类型实例化您的模板化容器时,编译器会生成 N 个略有不同的容器代码版本。 (与经典方法相比,您手动编写可以容纳任何类型对象的单个容器类......在运行时不安全,但只生成一个容器代码副本)
  • @Checkers:如果您的编译器无法折叠从模板生成的重复相同代码,那么有一些技术可以 - 安全! - 在代码中执行此操作(例如基于 void* 的所有 T* 实例,并在其周围使用精简的编译时包装器以确保类型安全)。
  • @anish 和 Checkers:您不需要包含通知的完整别名(而且您无法通知我;),请参阅blog.stackoverflow.com/2010/01/new-improved-comments-with-reply

标签: c++ templates generic-programming


【解决方案1】:

它们很难验证。不被使用的模板代码往往很少被编译。因此,良好的测试用例覆盖率是必须的。但是测试很耗时,然后可能会证明代码从一开始就不需要健壮。

【讨论】:

  • 这是一个我以前没有考虑过的有趣的问题。
  • 另一个问题可能是模板可用于防止代码编译:例如,确保类模板永远不会使用某些模板参数实例化。通常很难编写测试来验证代码无法编译。 :)
  • 这很好,当您最初可能不需要完整的接口时,很难确保所有模板方法都能正常处理各种数据类型。
【解决方案2】:

嗯,怎么样……

3:它们编译起来可能很慢

4:它们强制在编译时而不是运行时计算事物(如果您更喜欢快速执行速度而不是运行时灵活性,这也是一个优势)

5:较旧的 C++ 编译器不处理它们,或者不能正确处理它们

6:当您没有正确编写代码时,它们生成的错误消息几乎无法理解

【讨论】:

  • 我不同意 4. 在适当的时候使用模板,如果您需要运行时灵活性,请不要使用模板。 5 应该无关紧要,除非在旧代码库中抱怨一种语言没有意义。现代编译器要么免费,要么便宜。 6是不确定的。 :P 理解模板,错误是可以理解的。
  • 好吧,他要求任何缺点,而不仅仅是无法克服的缺点:)
  • @GMan:我不同意你关于#6 的 POV。对于看起来很无辜的std::list<int> l; std::sort(l.begin(),l.end()); VC9 会向您吐出 7.5kB 的错误消息,所有这些都指向 std lib 标头。即使我完全理解为什么以这种方式对列表进行排序不起作用,7.5kB 的错误消息也难以理解。
  • @GMan:这里有 28 条错误消息,一个指向我的代码,上面写着see reference to function template instantiation 'void std::sort<std::list<_Ty>::_Iterator<_Secure_validation>>(_RanIt,_RanIt)' being compiled - 这是一个非常我调用的扭曲版本。你应该看到当他们遇到其中一个时,谦逊的同事是如何接近我的办公桌的。第一个视图始终是他们编写的代码,因为通过查看您自己的代码(不是它导致的错误消息)来了解哪里出了问题通常是您最好的选择。 这是完全错误的
  • 我想说模板的 主要 缺点是错误消息很差。它们不容易理解,这是将概念添加到 C++0x 的部分原因:以改进诊断。我想说我非常了解模板,但即使是像在 unique_ptr 中存储的类上使用受保护的析构函数这样简单的错误,也让我思考了一段时间的一页长错误消息。没有帮助。 (是的,我这样做很愚蠢 :P 但人们一直都在指手画脚,这就是为什么好的诊断很重要)
【解决方案3】:

模板将您的实现暴露给代码的客户端,如果您在库边界传递模板化对象,这会使维护 ABI 变得更加困难。

【讨论】:

    【解决方案4】:

    到目前为止,似乎没有人提到我发现模板的主要缺点:代码可读性直线下降!

    我指的不是语法问题——是的,语法很丑,但我可以原谅。我的意思是:我发现对于以前从未见过的非模板代码,无论应用程序有多大,如果我从main() 开始,我通常可以毫无问题地解码程序正在执行的大致步骤。仅使用vector<int> 或类似代码的代码丝毫不会打扰我。但是,一旦代码开始定义和使用自己的模板来实现简单容器类型之外的目的,可理解性很快就会消失。 这对代码维护有非常负面的影响。

    其中一部分是不可避免的:模板通过复杂的偏序重载解析规则(对于函数模板)以及在较小程度上的偏特化(对于类模板)提供了更大的表达性。但是这些规则非常复杂,即使是编译器编写者(我很高兴承认他们比我聪明一个数量级)在极端情况下仍然会弄错。

    C++ 中命名空间、友元、继承、重载、自动转换和参数相关查找的交互已经足够复杂了。但是,当您将模板添加到组合中时,以及它们附带的名称查找和自动转换规则的细微变化时,复杂性可能会达到我认为没有人可以处理的程度。我只是不相信自己能够阅读和理解使用所有这些结构的代码。


    一个与模板无关的困难是调试器仍然难以自然地显示 STL 容器的内容(与 C 风格的数组相比)。

    【讨论】:

    • 模板元编程可能是一个维护问题,当然。但是更标准的模板用法是所有经常使用 C++ 的程序员都需要掌握的。我在调试 STL 容器方面有相反的经验,至少在 Visual Studio 中是这样。例如,std::vector 在监视窗口中显示为可折叠条目,可展开到容器的正确大小。对于 C 风格的数组,您必须使用更丑陋的“myArray,n”语法,手动指定数组的大小。 std::list 或 std::map 与手写对应物的区别更加显着。
    • 谢谢 Ben,是的,最令人头疼的是 TMP。这是关于 Visual Studio 的好消息,我一定是因为早期版本而感到沮丧。或者这只是非 Express 版本的功能?
    【解决方案5】:

    唯一真正的缺点是,如果您在模板中犯了任何微小的语法错误(尤其是其他模板使用的语法错误),错误消息不会有帮助...期待几页几乎无法使用的错误消息;-)。编译器的缺陷是非常特定于编译器的,语法虽然丑陋,但并不真正“复杂”。总而言之——尽管正确的错误诊断存在巨大问题——模板仍然是关于 C++ 的唯一最好的东西,这很可能会诱使你使用 C++ 而不是其他劣质语言泛型的实现,例如 Java...

    【讨论】:

    • +1 非常正确!希望 C++0x 中的概念有助于诊断。
    【解决方案6】:

    编译器解析它们很复杂,这意味着您的编译时间会增加。如果您有高级模板结构,也很难解析编译器错误消息。

    【讨论】:

      【解决方案7】:

      了解它们的人越来越少,尤其是在元编程层面,因此能够维护它们的人也越来越少。

      【讨论】:

        【解决方案8】:

        当您使用模板时,您的编译器只会生成您实际使用的内容。我不认为使用 C++ 模板元编程有任何缺点,除了编译时间可能会很长,如果你使用像 boost 或 loki 库那样非常复杂的结构。

        【讨论】:

          【解决方案9】:

          一个缺点:模板错误只有在模板被实例化时才会被编译器检测到。有时,模板的方法中的错误只在成员方法被实例化时才被检测到,而不管模板的其余部分是否被实例化。

          如果我在模板类的方法中有错误,只有一个函数引用,但其他代码使用没有该方法的模板,编译器将不会生成错误,直到错误的方法被实例化。

          【讨论】:

          • +1,但更准确的说法是在模板实例化之前没有检测到 一些 错误。语法错误和仅涉及非依赖类型的类型错误将在定义时发现。
          【解决方案10】:

          绝对最糟糕的情况:您从错误的模板代码中获得的编译器错误消息。

          【讨论】:

            【解决方案11】:

            这些年来我有时会使用模板。它们可能很方便,但从专业的角度来看,我正在远离它们。其中两个原因是:

            1.

            需要 a.) 将函数定义(不仅是声明)“源”代码暴露给“使用位置”代码,或者 b.) 在源文件中创建一个虚拟实例化。这是编译所需要的。选项 a.) 可以通过在头文件中定义函数或实际包含 cpp 来完成。

            我们容忍 C++ 中的标头(例如与 C# 相比)的原因之一是因为“接口”与“实现”的分离。好吧,模板似乎与这种理念不一致。

            2.

            模板类型参数实例化调用的函数可能不会在编译时强制执行,从而导致链接错误。例如。示例; example.CompilerDoesntKnowIfThisFunctionExistsOnT(); 这是“松散的”恕我直言。

            解决方案:

            而不是模板,我倾向于使用基类,派生/容器类在编译时知道什么是可用的。基类可以提供模板常用的通用方法和“类型”。这就是为什么如果需要修改现有代码以在需要的继承层次结构中插入通用基类,源代码可用性会很有帮助。否则,如果代码是封闭源代码,最好使用通用基类重写它,而不是使用模板作为解决方法。

            如果类型不重要,例如vector 那么只使用“对象”怎么样。 C++ 没有提供“对象”关键字,我已向 Bjarne Stroustrup 博士建议,这将有助于告诉编译器和阅读代码的人类型并不重要(对于不重要的情况)。我不认为 C++11 有这个,也许 C++14 会有?

            【讨论】:

              猜你喜欢
              • 1970-01-01
              • 2011-11-16
              • 2018-11-27
              • 2010-09-29
              • 2011-09-23
              • 1970-01-01
              • 2010-10-11
              • 2020-11-18
              • 1970-01-01
              相关资源
              最近更新 更多