2

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

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

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

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

我发现了一个类似的问题,但它并没有完全回答我的问题:C++ 模板类是否为使用的每种指针类型重复代码?

非常感谢!

4

2 回答 2

5

我有两个关于 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 生成专用代码(考虑到它们具有相同的大小并且没有专门化)。

它们是不同的类型。我可以编写与 a charorshort值不同的代码。举个例子:

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

x 为 ashort时,打印一个整数,其值为x-x为 achar时,打印对应于 的字符x

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

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

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

于 2013-03-24T15:32:54.017 回答
5

答案是也许。通常,模板的每个实例化都是唯一的类型,具有唯一的实现,并且会产生完全独立的代码实例。合并实例是可能的,但将被视为“优化”(根据“好像”规则),并且这种优化没有广泛传播。

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

  • C++ 默认使用值语义。std::vector例如,一个实际上会插入副本。无论您是复制 ashort还是 adouble都会对生成的代码产生影响。在Java中,short并且double会被装箱,并且生成的代码会以某种方式克隆一个装箱的实例;克隆不需要不同的代码,因为它调用 的虚函数Object,但物理复制却需要。

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

另一个问题是 C++ 中模板的实现必须存在于头文件中。当然,在 Java 中,一切都必须始终存在,因此这个问题会影响所有类,而不仅仅是模板。当然,这也是 Java 不适合大型应用程序的原因之一。但这意味着您不希望模板中有任何复杂的功能;与 Java(和许多其他语言)相比,这样做会失去 C++ 的主要优势之一。事实上,在模板中实现复杂功能时,模板继承自非模板类的情况并不少见,该类在void*. 在实现大块代码的同时void*从来都不是有趣的,它确实具有为客户提供两全其美的优势:实现隐藏在编译文件中,以任何方式、形状或方式对客户不可见。

于 2013-03-24T14:28:41.073 回答