在C
中,可以进行部分编译,因为整个*.c
文件可以编译成机器代码,其中包含解析和重定位,供链接器处理。这只是计算某些指令在最终可执行文件中的位移或知道某个全局变量的绝对地址的问题。
在C++
中,似乎可以完成相同的操作 - C++
代码和等效的C
代码之间存在相当简单的映射(就编程语言之间的映射而言) )。但是,模板似乎使事情变得复杂。
例如,如果我在std::vector<int>
中使用1.c
,那么,由于模板类是由<vector>
标头指定的,因此编译器可以为{{{{}}生成机器代码1}}规范。假设在同一个项目中有一个文件int
,它还依赖于2.c
专门化,并且必须链接std::vector<int>
和1.o
。是否可以将2.o
和1.c
部分编译到他们自己的2.c
文件以便以后链接?
正如下面评论中的链接问题中所提到的,这个问题有两种常用的方法:生成*.o
代码,或者链接器经历另一轮“依赖编译”,其中单个{{ 1}}被编译,然后链接到两个文件。
关于“贪婪编译” - 这是否意味着每个编译单元中每次使用模板类方法都必须放在链接器重定位表中?此外,某些调用可能不使用长跳转(即,模板类在使用它的方法的正上方定义)。但是,如果链接器要强制编译单元使用它所选择的特化,则需要进行长跳转 - 但是指令大小太大而无法修补。
答案 0 :(得分:3)
这是一个比大多数人意识到的更复杂的问题。
在一般和最简单的情况下,模板定义出现在标题中,它的行为与内联函数相同。编译器将为需要它们的每个转换单元中所需的那些函数生成代码。然后链接器将通过删除除一个之外的所有符号来解析重复的符号由于标准要求它们完全等效,链接器可以从列表中选择任何一个。
如果模板只需要使用几种类型,您可以将定义移动到单个翻译单元,并在那里显式实例化那些类型的模板。在一般情况下,这将表现为非内联函数。
介于两者之间,如果模板可以用任何类型实例化,但通常用少数几个实例化,模板的实现者可以使用混合方法,其中模板和成员在头文件中定义,但也声明了显式实例化。然后在单个翻译单元中,可以完成那些显式实例化。
例如,可以使用此方法在使用std::string
(真正std::basic_string<char, std::char_traits<char>, std::allocator<char> >
)时最小化编译和链接时间。编译器可以在单个转换单元中提供公共实例化的所有函数,但仍然在头文件中提供模板函数的定义,这样如果您选择使用basic_string
模板的不同实例化它仍然会为你工作。在仅使用std::string
的所有翻译单元中,编译器知道不为所有成员生成代码,因为链接器可以使用这些代码。
答案 1 :(得分:1)
编译器为模板的每个实例化生成代码,并确保没有名称冲突。它与普通函数不同,如果在两个编译单元中使用.cpp文件,则会出现链接器错误。
可以通过在某些编译单元中显式实例化模板来节省一些编译时间,并在其他地方使用该模板,但这需要相当多的手动内务处理(为项目中使用的每个新类型添加显式实例化) 。您还可以通过在模板化构造函数上使用关键字explicit来避免一些不必要的转换来节省一些编译时间。