模板的分离编译

本文探讨了C++中模板的两种编译模式:包含编译和分离编译。重点介绍了分离编译模式下如何解决类模板定义的问题,包括三种解决方法,并详细说明了显式实例化声明的作用。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

< >中谈到过,模板的文件组织一节中就讲到了这个问题. 书中提到了2中方法:

 第一种:实现和声明放在一个编译单位中,这样这个模板定义所依靠的一些东西就被带到了包含这个编译单位的文件里。

坏处是:增大编译器的处理信息量,而且用户会意外的依赖了原本只是为定义这个模板而引进的东西。

解决方案:使用名字空间、避免宏定义、以及一般性的减少包含进来的信息量,可以减小这种危害影响。

 第二种:分开编译。这种处理就和一般的非inline函数处理一样。不过这里需要用到export关键字了,export关键字的作用就是告诉编译器这个东西别的编译单位可以访问;否则按照一般规则,任何地方使用模板,都要求其定义在作用域中。

这种方法的优点是:思路清晰。

缺点:因为模板被单独编译了,因此在需要时候,找到合适的定义成了实现者(编译器的责任),要实现者在需要时候找到唯一的一个版本,想想编译器这家伙可就头疼了,因此很少编译器做到这一点。 

总结了上面的两种情况后,我们可以看到,第一种在编译时候,任何需要这个模板的地方都引用了这个模板定义,因此编译器据此来优化,去掉冗余的版本。

第二种方法就给实现者(编译器)增加了负担,不是去掉冗余的版本,而是在需要时找到这个唯一版本。 本人也在自己的VS2005上测试,出现提示 C/C++ code.convert.cpp(6) : warning C4237: 目前还不支持“export”关键字,但已保留该关键字供将来使用

 

filename: seqlist.h 类模板的申明
////////////////////////////////////////////////////////////
#ifndef SEQLIST
#define SEQLIST

template
<calss T>
class SeqList
{
 
}


////////////////////////////////////////////////////////////
// filename: seqlist.cpp 类模板的定义
////////////////////////////////////////////////////////////
#include"seqlist.h"

template
<class T>
SeqList
<T>::SeqList()
{
 
}


////////////////////////////////////////////////////////////
// filename: main.cpp 主函数
////////////////////////////////////////////////////////////
#include"seqlist.h"
#include
<iostream>
int main()
{
 
}

 

如以上写法,连接时会出错,这个问题属于类模板的分离编译,有三种解决办法:
1. 将类模板申明与类模板定义放到一个文件中。
2. 在主函数中加 #include"seqlist.cpp"。
3. 使用export关键字。

c++primer中讲到"c++的模板编译模式"有两种——"包含编译和分离编译"。详见c++primer 16.8


///////////////////////////////////////////////////////////////

在讨论分离编译之前,首先来看一下包含编译:

一、包含编译模式

        在包含编译模式下,我们在每个模板被实例化的文件中包含函数模板的定义,并且往往把定义放在头文件中,象内联函数所做的那样。如:

        // model1.h
        // 包含模式:模板定义放在头文件中
        template <typename Type>
              Type min( Type t1, Type t2 ) {
                        return t1 < t2 ? t1 : t2;
        }

        在每个使用min()实例的文件中都包含了该头文件,如:

        // 在使用模板实例之前包含模板定义
        #include "model1.h"
        int i, j;
        double dobj = min( i, j );

        该头文件可以被包含在许多程序文本文件中。这意味着编译器必须在每个调用该实例的文件中实例化min()的整型实例吗?不。该程序必须表现得好像min()的整型实例只被实例化一次。但是,真正的实例化动作发生在何时何地,要取决于具体的编译器实现。

二、分离编译模式

        在分离编译模式下,函数模板的声明被放在头文件中。在这种模式下,函数模板声明和定义的组织方式与程序中的非内联函数的声明和定义组织方式相同。如:

        // model2.h
        // 分离模式:只提供模板声明
        template <typename Type>  Type min( Type t1, Type t2 );

        // model2.c
        // 模板定义
        export  template <typename Type>
                Type min( Type t1, Type t2 ) {/* . . . */}

        使用函数模板min()实例的程序只需在使用该实例之前包含这个头文件:

        // user.c
        #include "model2.h"
        int  i, j;
        double d = min( i, j ); // OK: 用法,需要一个实例

        我们通过在模板定义中的关键字template之前加上关键字export,来声明一个可导出的函数模板。

三、显式实例化声明

        标准C++提供了显式实例化声明来帮助程序员控制模板实例化发生的时间。在显式实例化声明中,关键字template后面是函数模板实例的声明,其中显式地指定了模板实参。下例中提供了sum(int* , int)的显式实例化声明:

        template <typename Type>
              Type sum( Type op1, int op2 ) {/* . . . */} // 函数模板sum的定义必须给出
        // 显式实例化声明
        template int* sum< int* >( int*, int );

        该显式实例化声明要求用模板实参int*实例化模板sum()。对于给定的函数模板实例,显式实例化声明在一个程序中只能出现一次。


<think>嗯,用户问的是模板为什么不支持分离编译。首先,我得回忆一下C++模板的基本机制。模板编译时需要进行实例化,也就是说编译器在看到模板定义的时候,必须知道具体的类型参数才能生成对应的代码。如果模板的声明和实现分开写在头文件和源文件里,那么当其他源文件包含这个头文件时,编译器只能看到声明而看不到实现,这时候实例化就会失败。 比如,用户可能写了一个模板类在头文件声明,在cpp文件实现。然后在另一个cpp文件中使用这个模板的不同类型参数,比如int和double。这时候编译器编译使用这些参数的源文件时,无法找到对应的实现代码,导致链接错误。因为模板的实现没有被实例化到目标文件中。 然后,解决方法有几个。第一个是把模板的定义和声明都放在头文件里,这样包含头文件的时候,编译器就能看到完整的定义,进行实例化。第二个是显式实例化,也就是在源文件中预先实例化需要的类型,但这限制了只能使用那些预先实例化的类型。第三个是用export关键字,不过我记得这个关键字在C++11之后被移除了,所以可能不适用。 用户可能遇到的问题是在实际项目中,当模板代码分离到不同文件时出现的链接错误。这时候需要解释清楚原因,并给出可行的解决方案。例如,建议将模板的实现也放在头文件中,或者使用显式实例化来处理特定的类型。 另外,可能需要提醒用户注意模板代码组织的最佳实践,避免分离编译带来的问题。还有,现代C++中是否还有其他方法,比如使用inline或constexpr等,不过这些可能不直接解决分离编译的问题。</think><< 模板不支持分离编译的原因在于:C++的编译模型要求模板的**完整定义**必须在实例化时可见。当模板的声明和实现分离到不同文件(.h和.cpp)时,编译器编译调用模板的代码时无法生成具体实例化的代码,导致链接错误。 ### 具体原因: 1. **编译阶段限制** 模板的实例化发生在编译阶段。若模板的实现(成员函数/静态成员)在.cpp文件中,其他.cpp文件调用模板时仅能看到声明,无法生成具体类型的机器码。 2. **符号缺失问题** 若模板未被显式实例化,链接器在最终链接时找不到对应类型的实现代码(如`MyTemplate<int>::foo()`),导致`undefined reference`错误。 --- ### 解决方法: #### 1. 将模板定义放在头文件中(最常见) ```cpp // mytemplate.h #pragma once template <typename T> class MyTemplate { public: void foo() { // 实现直接写在头文件中 } }; ``` #### 2. 显式实例化模板(适用于已知类型) ```cpp // mytemplate.cpp #include "mytemplate.h" // 显式实例化需要的类型 template class MyTemplate<int>; // 仅支持int类型 template class MyTemplate<double>; // 仅支持double类型 ``` #### 3. 使用`export`关键字(C++11已废弃) ```cpp // 注意:主流编译器(如GCC、Clang)均不支持此特性 export template <typename T> class MyTemplate { /* ... */ }; ``` --- ### 示例:分离编译错误场景 ```cpp // mytemplate.h template <typename T> class MyTemplate { public: void foo(); // 只有声明 }; // main.cpp #include "mytemplate.h" int main() { MyTemplate<int> obj; obj.foo(); // 链接错误:找不到MyTemplate<int>::foo() } ``` --- ### 根本原理: C++标准规定模板是**编译期多态**机制,编译器需要根据调用处的具体类型生成代码。若模板实现不可见(如分离到.cpp文件),则无法完成实例化。 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值