在我们做c++开发的过程中,通常我们写一个类就会有一个.h和.cpp文件,当我们需要调用一个函数或者引用一个变量时我们只需要包含声明这些函数或者变量的.h文件即可,当我们编译c++项目时,每个cpp文件被编译成一个.obj文件,在这些.obj文件只包含非本文件函数的声明而没有这些函数的二进制代码,当我们调用这些函数时会生成一个call指令去调用函数,当在自己的.obj文件里找不到关于该函数的二进制文件时,编译器会交给c++连接器linker,此时linker会在其他.obj文件里寻找关于这些函数真正的定义找到相关的二进制代码,然后执行该函数。
这样做有它的好处,首先我们一个项目不可能都写在一个文件里,这样有利于项目模块的划分,其次有利于我们快速的定位项目中的错误。
但是当分离式编译碰到c++模版的时候就不灵了,要知道模版在实例化之前是不会被编译成二进制的,当我们有一个c++模版类的时候,此类在实例化之前是不会被编译成二进制代码的,而且对于模版类的函数,如果我们没有在它所在的.obj文件里被调用是不会被实例化的,也就是说这些函数不会生成相应的二进制代码的,此时如果我们调用这些函数c++编译器就会给出我们最不愿看到的编译错误了。因此我们不得不把模版类的定义和声明放在同个一个文件里。如果我们非要分开呢。
下面我们看一个模版例子
//
// Header.h
// Demo
//
// Created by 杜国超 on 17/2/15.
// Copyright © 2017年 杜国超. All rights reserved.
//
#ifndef Header_h
#define Header_h
#include
#include
#define SIXDAY_RANK_NUM 10
template
class TemplateClassA
{
public:
TemplateClassA();
void init(T Id,int iScore);
void reset();
public:
T mId;
int miScore;
};
template
class TemplateClassB
{
public:
TemplateClassB();
~TemplateClassB();
void reset();
void say();
public:
std::array
, uiRankNum> maRanklist; }; class XRankUser { public: XRankUser(); ~XRankUser(); void init(); void ScoreRanksSay(); public: TemplateClassB
ScoreRanks; //#####标记1###### }; #endif /* Header_h */
//
// Header.cpp
// Demo
//
// Created by 杜国超 on 17/2/15.
// Copyright © 2017年 杜国超. All rights reserved.
//
#include "../inc/Header.h"
template
TemplateClassA
::TemplateClassA()
{
}
template
void TemplateClassA
::init(T Id, int iScore)
{
mId = Id;
miScore = iScore;
}
template
void TemplateClassA
::reset() { miScore = 0; } template
TemplateClassB
::TemplateClassB() { } template
TemplateClassB
::~TemplateClassB() { reset(); //#####标记1###### } template
void TemplateClassB
::reset() { printf("TemplateClassB reset \n"); } template
void TemplateClassB
::say() { printf("TemplateClassB say \n"); } XRankUser::XRankUser() { } XRankUser::~XRankUser() { } void XRankUser::init() { } void XRankUser::ScoreRanksSay() { ScoreRanks.say(); //#####标记2###### }
#include
using namespace std;
#include "inc/Header.h"
int main(int argc, const char * argv[])
{
XRankUser rank;
rank.ScoreRanksSay(); //#####标记1######
rank.ScoreRanks.reset(); //#####标记2######
rank.ScoreRanks.say(); //#####标记3######
TemplateClassB
ScoreRanks; //#####标记4######
return 0;
}
首先现在的代码结构,模版的声明和定义是分开的,并且是可以编译通过的,但是要注意,如果我们注释header.h文件里的标记1的代码(注意此时用到此成员变量的地方也要相应注释掉),main函数中的标记4处的代码将会报错,打开注释运行正常,因为标记1处的声明导致了在构造函数中调用了相关模版的构造函数,此时构造函数被编译,所以才编译通过。同理如果我们去掉header.cpp中的标记1,那么main函数的标记2会报错,去掉header.cpp的标记2main函数的标记3会报错,当然我们通常会通过main函数标记1代替标记3的代码,这里只是为了说明问题,可见在.cpp文件中被调用过的函数是不受模版的影响的,这也就证明了在相应的.cpp文件中没有被调用的函数没有背编译进.obj文件中。
因此我们在写c++模版的时候两种方式来避免分离式编译对模版的影响,一是把声明和定义放在同一个文件里,二就是在模版所在的.obj文件里调用模版的函数,就像header头文件和cpp文件标记的代码一样。