报价从c + + 标准库︰ 教程和手册:

目前使用模板仅可移植的方式是使用内联函数的头文件中实现它们。

这是为什么?

(说明︰ 头文件并不是唯一的便携解决方案。但它们是最方便的便携解决方案。

2009-01-30 10:06:50
问题评论:

其中有阅读的?

问题是不正确的。还有另一种可移植的方法。模板类可以显式实例化-正如已指出通过其他答案。

这个问题是类型的"为什么是事实吗?"当事实有误。

的确如此,在头文件中放置所有模板函数的定义可能是最方便的方法来使用它们,而是仍然不清楚的"内联"作用在该报价单。没有必要使用的内联函数。"内联"都绝对没有做到这些。

考虑到这个问题有这么多的 upvotes,我们应当试着对其进行编辑并改进它。问题是不正确的。模板仅有至少一个翻译单元中进行实例化,它不在意的翻译单元。我是否正确,这是完全可移植的?我愿意在我错误的情况下编辑上我自己的问题

回答:

它不需要将实现放在头文件中,请参见其他可选择的解决方案,在此答案的末尾。

不管怎样,您的代码出现故障的原因是,当实例化模板,编译器创建一个新类与给定的模板参数。例如︰

template<typename T>
struct Foo
{
    T bar;
    void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f; 

当读取此行,编译器将创建一个新类 (让我们称之为FooInt),这相当于下列︰

struct FooInt
{
    int bar;
    void doSomething(int param) {/* do stuff using int */}
}

因此,编译器必须具有访问权限的方法,以便它们进行实例化的模板参数 (在此案例int) 与实现。如果这些实现不是标头中,是不能被访问,并且因此,编译器就无法实例化模板。

常见的解决方案是编写模板声明在头文件中,然后在实现文件 (例如.tpp) 中实现的类和包含此标头信息末尾处的实现文件。

// Foo.h
template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

// Foo.tpp
template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}

这种方式,实现仍然分开声明中,但编译器可以访问。

另一个解决方案是保留实现中分离出来,并将需要的所有模板实例进行显式实例都化︰

// Foo.h

// no implementation
template <typename T> struct Foo { ... };

//----------------------------------------    
// Foo.cpp

// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float

如果我解释不足够明确,您可以查看有关该主题的 c + + 超级常见问题解答.

显式实例化实际需要为其有权访问所有 Foo 的成员函数,定义的.cpp 文件中,而不是标头中。

"编译器需要有权访问的方法,它们与模板参数 (在本案例为 int) 进行实例化的实现。如果这些实现不标头中,它们不是访问",但为什么不在.cpp 文件中实现编译器可以访问?编译器还可以访问.cpp 信息,如何将其变为它们.obj 文件?编辑︰ 对此问题的答案就在此应答中提供的链接...

我并不认为这可以说明这个问题很显然,关键的一点显然相关使用编译单元它不在这篇文章中提到

@Gabson︰ 结构和类都具有默认访问修饰符的类是"私有",而是公共结构的异常等。还有其他一些通过观察这个问题,您可以学习的微小差异.

@mrgloom︰ 是的函数模板可以像类模板实例化的 explicitely juste (请参阅如何 explicitely 实例化的模板函数).

很多正确答案在这里,但我想要添加此 (完整性)︰

否则,底部的实现 cpp 文件中,将与使用该模板的所有类型的显式实例化,链接器将能够像往常一样找到它们。

编辑︰ 添加显式模板实例化的示例。使用后定义的模板,并且已定义的所有成员函数。

template class vector<int>;

将实例化 (并因此使链接器) 类和所有其成员函数 (只)。类似适用于模板的功能,因此如果您有非成员运算符重载可能需要为那些执行相同的操作。

上面的示例中是相当无用,因为向量完全定义在头中,除了常见的包含文件 (预编译头?) 使用extern template class vector<int>以防止其在其他所有使用向量 (1000)? 文件初始化完成时。

你是最好的。有好几个小时找到不涉及将实现或头文件中包含的解决方案。

重要说明︰ 上 Clang 甚至其他编译器,它应该是template class vector<int>;否则为它将不起作用。

嗯。很好,但没有真正清理解决方案。列出所有可能的类型的模板似乎不与内容模板应在转。

这可以是很好,在许多情况下,但通常会中断其旨在使您可以使用与任何type的类而无需手动列出它们的模板的用途。

vector不是很好的例子,因为容器本身就面向"全部"类型。但它确实发生非常频繁地创建仅用于一组特定的类型,例如数字类型的模板︰ int8_t,int16_t,int32_t,uint8_t,uint16_t,等等。在这种情况下,它仍然很重要,要使用的模板,但显式实例化类型的整个集也可能是,在我看来,推荐。

正是因为单独编译的必要条件,因为模板实例化样式的多态性。

让有点接近具体的说明。说我使用了以下文件︰

  • foo.h
    • 声明的接口的class MyClass<T>
  • foo.cpp
    • 定义class MyClass<T>的实现
  • bar.cpp
    • 使用MyClass<int>

单独的编译意味着我应该能够编译独立于bar.cpp foo.cpp编译器执行分析、 优化,以及在每个编译单元的代码生成的所有辛勤的工作完全独立;我们不需要做分析整个程序。它是只需要一次,处理整个程序链接器,链接器的作业是方便很多。

甚至不必存在编译foo.cpp,但我仍然可以链接我已经和我已经只是生产,而无需重新编译foo.cpp bar.o foo.o bar.cppfoo.cpp甚至可以编译到动态库,分发别处没有foo.cpp,并与它们编写多年后我写了foo.cpp代码链接.

"实例化样式多态性"意味着模板MyClass<T>真的不可以编译为代码,可以适用于任何的T值的泛型类。它将如装箱,将函数指针传递给分配器和构造函数等所需的系统开销。C + + 模板的目的是要避免编写几乎相同的class MyClass_intclass MyClass_float等,但仍能采已编译的代码,通常是像我们一样分别写入每个版本。因此,模板是按其原义模板;类模板类,它是我们遇到的每个T为创建一个新类食谱。模板不能编译成代码,可以编译只实例化模板的结果。

因此foo.cpp编译时,编译器不能发现bar.cpp知道, MyClass<int>它可以看到模板MyClass<T>,但它不能为此发出的代码 (它是一个模板,而不是类)。Bar.cpp编译时,编译器可以看到其需要创建MyClass<int>,但它无法看到模板MyClass<T> (仅在foo.h的接口),所以不能创建它。

如果foo.cpp本身使用MyClass<int>,该代码将生成编译foo.cpp,以便bar.o链接到foo.o时可以挂钩并起作用时。我们可以使用这一事实允许一组有限的模板实例化在.cpp 文件中通过编写一个模板来实现。但有没有办法bar.cpp来使用模板作为模板和实例化此上任何类型它喜欢;它可以仅使用预先存在版本的foo.cpp的作者认为提供的模板类。

您可能会认为,当编译模板编译器应"生成所有版本",与那些从未筛在链接过程中使用的也是。除了巨大的开销,因为,这种方法将会面临的极端困难等"类型修饰符"功能的指针和数组允许甚至只是内置类型,引来无数的类型,我现在通过添加扩展我的程序时,会发生什么情况︰

  • baz.cpp
    • 声明和实现class BazPrivate,并使用MyClass<BazPrivate>

没有可能的方法,这能起作用,除非我们要么

  1. 需要重新编译foo.cpp每次我们更改程序中的任何其他文件时,它在用例中添加MyClass<T>新新颖实例化
  2. 要求包含该baz.cpp (可能是通过头包括中) 的完整模板的MyClass<T>,以便baz.cpp在编译期间,编译器可以生成MyClass<BazPrivate>.

没有人喜欢 (1),因为整个程序分析编译系统要不停地进行编译,而且它使得无法分发没有源代码编译的库。所以,我们有的是 (2)。

您最后一个段落提供了为什么这些事情的感谢您很大程度的极好说明。

强调的报价单模板是按其原义的模板; 类模板不是类,它是用于创建一个新的类,我们遇到了每个 t 食谱

模板必须由编译器实例化实际上将它们编译到对象代码之前。如果模板参数已知,可只有实现此实例化。现在,假设a.h中声明、 在a.cpp中定义和在b.cpp中使用 tempate 函数的其中一个方案。a.cpp编译时,不一定知道即将进行编译b.cpp需要实例的模板,更别提是的是哪些特定的实例。头和源代码文件,这种情况很快就会更加复杂。

有人可以会说,compiers 可以做似乎更明智些"展望"的所有使用的模板,但我敢肯定它不会很难创建递归或否则复杂的方案。AFAIK,compliers 不执行此类外观的预。Anton 指出,某些编译器支持显式导出 decarations 的 tempate 实例,但不是所有编译器都支持它 (尚)?。

"导出"是标准,但它是真的难以实施,这样大部分编译器团队只是还没有这样。

导出不会消除对源代码泄漏的需要也不它减少编译依赖关系,尽管它要求编译器构建者的大规模工作。所以等 Sutter 自己要求编译器生成器以忘记导出。作为时间所需的投资将会是更好地花费在其他地方...

因此,我并不认为尚未实现出口。它将可能永远不会完成任何人其他比 EDG 后其他人看到了多长时间,以及如何获得很小

如果感兴趣,纸叫做"为什么我们不能导出",它将列在他的博客 (gotw.ca/publications),但那里没有 pdf (快速的 google 应该将其通过)

这是更准确地比较与第一个回答。

实际上,c + + 标准定义的导出关键字使您能够只是声明的标头中的模板文件中,并实现它们在其他地方。

遗憾的是,没有任何流行的编译器实现此关键字。我知道只有一个是编写 Edison 设计组中,由 Comeau c + + 编译器的前端。所有其他人去编写模板,在头文件中,因为编译器需要正确实例化代码的定义 (正如其他人指出已经) 堵塞。

结果导出模板被从 C + + 11。

@johannes︰ 没有捕获的感谢。事实上,您无法export仍由于使用,将使用 Comeau 编译器将您。它是"死功能;"我只是一个会多么喜欢可以随处看到实现。

.几年以后,a 厎而且我最后理解export哪些会实际上具有给定我们,和什么不...,现在我全面同意 EDG 人︰它将不具有为我们带来什么大多数人 (自己在 ' 11 包含)认为如此,和 c + + 标准是没有它的最好

@DevSolar︰ 本文是政治、 重复性和编写不正确。这不是通常标准级别枯燥文字活泼起来有。Uneedingly 长且令人厌烦,上万页跨基本上说三次同样的操作。但我现在要通知导出不可导出。这就是很好的英特尔 !

@v.oddou︰ 很好的开发人员和良好的技术文档撰稿人是两个单独的技能。一些可以兼具,很多不能。;-)

请输入您的翻译

Why can templates only be implemented in the header file?

确认取消