【问题标题】:Will C++ compiler generate code for each template type?C++ 编译器会为每种模板类型生成代码吗?
【发布时间】:2013-03-14 00:42:05
【问题描述】:

我有两个关于 C++ 模板的问题。假设我已经编写了一个简单的 List,现在我想在我的程序中使用它来存储指向不同对象类型(A*、B* ... ALot*)的指针。我的同事说,对于每种类型,都会生成一段专用的代码,即使所有指针实际上都具有相同的大小。

如果这是真的,有人可以解释一下为什么吗?例如,在 Java 中,泛型与 C++ 中的指针模板具有相同的用途。泛型仅用于预编译类型检查,并在编译前被剥离。当然,所有内容都使用相同的字节码。

第二个问题是,是否还会为 char 和 short 生成专用代码(考虑到它们的大小相同且没有专门化)。

如果这有什么不同,我们正在谈论嵌入式应用程序。

我发现了一个类似的问题,但它并没有完全回答我的问题:Do C++ template classes duplicate code for each pointer type used?

非常感谢!

【问题讨论】:

  • 它怎么没有完全回答你的问题?它没有回答什么?
  • charshort 大小相同,char 为 1 个字节,short 为 2 个字节。
  • Java 泛型与 C++ 模板几乎没有共同之处。除了<> 语法可能。
  • 另外,不要泛化泛型。有多种类型的泛型。 Java 对泛型使用了非常可怕的类型擦除方法。 .NET 语言(C#、VB 等)使用具体化的泛型。精化的泛型在编译之前不会“剥离”。
  • @Mat 我知道,我想在注意到之后编辑评论说“通常”,但现在不可能了。

标签: c++ templates


【解决方案1】:

我有两个关于 C++ 模板的问题。假设我编写了一个简单的 List,现在我想在我的程序中使用它来存储指向不同对象类型(A*、B* ... ALot*)的指针。我的同事说,对于每种类型,都会生成一段专用的代码,即使所有指针实际上都具有相同的大小。

是的,这相当于编写了两个函数。

一些链接器会检测到相同的功能,并消除它们。一些库意识到他们的链接器没有此功能,并将通用代码分解为单个实现,只留下围绕通用代码的强制转换包装器。即,std::vector<T*> 专业化可以将所有工作转发给std::vector<void*>,然后在退出时进行强制转换。

现在,comdat 折叠很微妙:使您认为相同的函数相对容易,但最终却不一样,因此生成了两个函数。作为一个玩具示例,您可以通过typeid(x).name() 打印类型名。现在每个版本的功能都是不同的,不能被淘汰。

在某些情况下,您可能会认为它是一个不同的运行时属性,因此会创建相同的代码,并消除相同的函数 - 但智能 C++ 编译器可能会弄清楚您做了什么,使用 as-if 规则并将其转换为编译时检查,并阻止不完全相同的函数被视为相同。

如果这是真的,有人可以解释一下为什么吗?例如,在 Java 中,泛型与 C++ 中的指针模板具有相同的用途。泛型仅用于每次编译的类型检查,并在编译前被剥离。当然,所有内容都使用相同的字节码。

不,他们不是。泛型大致相当于类型擦除的 C++ 技术,例如 std::function<void()> 用于存储任何可调用对象的操作。在 C++ 中,类型擦除通常通过模板完成,但并非所有模板的使用都是类型擦除!

C++ 对本质上不是类型擦除的模板所做的事情对于 Java 泛型通常是不可能的。

在 C++ 中,您可以使用模板创建一个类型擦除的指针容器,但 std::vector 不会这样做——它会创建一个实际的指针容器。这样做的好处是std::vector 上的所有类型检查都是在编译时完成的,因此不必进行任何运行时检查:安全的类型擦除std::vector 可能需要运行时类型检查和相关的涉及开销。

第二个问题是,是否还会为 char 和 short 生成专用代码(考虑到它们具有相同的大小并且没有专门化)。

它们是不同的类型。我可以编写具有 charshort 值的行为不同的代码。举个例子:

std::cout << x << "\n";

x 是short,这会打印一个整数,其值为x -- 其中xchar,这会打印与x 对应的字符。

现在,几乎所有模板代码都存在于头文件中,并且隐含为inline。虽然inline 并不意味着大多数人认为的意思,但它确实意味着编译器可以轻松地将代码提升到调用上下文中。

如果这有什么不同,我们正在谈论嵌入式应用程序。

真正重要的是您的特定编译器和链接器是什么,以及它们具有哪些设置和标志。

【讨论】:

    【解决方案2】:

    答案是也许。一般来说,每个实例化 模板是一种独特的类型,具有独特的实现,并且 将产生一个完全独立的代码实例。 合并实例是可能的,但会考虑 “优化”(在“好像”规则下),以及这种优化 没有广泛传播。

    关于与Java的比较,有几点 要记住:

    • C++ 默认使用值语义。一个std::vector,用于 例如,实际上会插入副本。而无论你是 复制 shortdouble 确实会有所不同 生成的代码。在 Java 中,shortdouble 将被装箱, 并且生成的代码将以某种方式克隆一个装箱的实例; 克隆不需要不同的代码,因为它调用虚拟 Object 的功能,但物理复制可以。

    • C++ 比 Java 强大得多。特别是,它允许 比较函数地址之类的东西,它需要 模板的不同实例化中的函数具有 不同的地址。通常,这不是重点, 我可以很容易地想象一个编译器有一个选项告诉 它忽略这一点,并合并实例 在二进制级别相同。 (我认为 VC++ 有类似的东西 这个。)

    另一个问题是模板在 C++ 中的实现 必须存在于头文件中。当然,在 Java 中, 一切都必须始终存在,所以这个问题会影响所有 类,而不仅仅是模板。这当然是其中之一 Java 不适合大型应用程序的原因。但 这意味着您不想要任何复杂的功能 模板;这样做会失去 C++ 的主要优势之一, 与 Java(和许多其他语言)相比。其实并不是 很少见,在模板中实现复杂的功能时, 让模板从非模板类继承 根据void* 完成大部分实现。尽管 在void* 方面实现大块代码永远不会 有趣,它确实具有提供两者最好的优势 客户的世界:实现隐藏在编译中 文件,以任何方式、形状或方式对客户不可见。

    【讨论】:

    • 嗨詹姆斯,非常感谢您的回答!我可以争论 Java 不适合大型应用程序 :-) 但我不会。我的问题只是关于模板列表和其他容器,它们绝对不包含可以操作这些对象的逻辑,除了存储它们。如果你说可以合并完全相同的二进制代码,我很高兴。
    • @YuriyKulikov 我说它可以在“好像”规则下合并,而不是它会被合并。对于std::list 之类的东西,代码都是内联且微不足道的;内联之后,可能没有什么可以合并了。 (当然,这是 JIT 编译器在 Java 中所做的事情。形式上,Java 模板的实例化充满了类型转换,对应于 C++ 中的 dynamic_cast,并且在运行时相当昂贵。实际上, JIT 将能够消除大部分(如果不是全部)已检查的转换,并执行相当于 static_cast 的操作。)
    猜你喜欢
    • 1970-01-01
    • 2013-04-01
    • 1970-01-01
    • 2020-05-29
    • 2019-05-08
    • 2017-02-01
    • 2023-03-28
    • 2012-05-16
    • 1970-01-01
    相关资源
    最近更新 更多