结论

  • 不建议将模板类的声明和定义分开,一般把这个文件叫做 .hpp,以便与 .cpp 文件进行区分,表示这个文件中使用了模板
  • 模板类进行了两次编译,在第二次编译(模板类使用的时候)时才会生成真正的调用函数
  • 声明和定义分开方式一:可将定义放在同名的 .inl.tpp 文件中,同时在头文件的末尾使用 #include 包含进来,不可放到 .cpp 文件,否则会造成嵌套包含
  • 声明和定义分开方式二:在头文件中声明实例化,并在一个源文件中定义它,这样它只会被实例化一次,而不是每个翻译单元,这可能会加快编译速度,但是只能使用已经显式声明的模板类实例
  • 不建议在模板类中使用友元函数

声明和定义在同一文件

// Test.h
// 写法二:前置声明
template<typename T> class Test;
template<typename T> void printTest(Test<T>& t);

template<class T>
class Test
{
public:
    Test(T t){ m_t = t;};
    // explicit Test(T t){ m_t = t;};
    
    // 1. 直接在类内定义
    // T getValue(){
    //     return m_t;
    // };
    // 2. 类外定义
    T getValue();

    // 3. 不建议在模板类中使用友元
    // 写法一:不建议这种写法,与编译器有关
    // VS 编译通过
    // g++ 编译失败
    // template<typename T>    
    // friend void printTest(Test<T>& t);
    
    // 写法二:
    // 首先需要前置声明
    friend void printTest<T>(Test<T>& t);
    
private:
    T m_t;
};
// 类外定义
template<typename T>
T Test<T>::getValue(){
    return m_t;
};

template<typename T>
void printTest(Test<T>& t){
    cout << typeid(t.m_t).name() << endl;
    cout << t.m_t << endl;
}

// main.cpp
int main()
{
    Test<int> tInt(1);
    cout << tInt.getValue() << endl;

    Test<double> tD(1.15);
    cout << tD.getValue() << endl;
    
    Test<float> tF = 1.3f;                 // 构造函数没有explicit修饰,可以隐式转换
    cout << tF.getValue() << endl;

    cout << "friend function------------\n";
    printTest(tF);
    return 0;
}

输出:

Start
1
1.15
1.3
friend function------------
f
1.3
Finish

声明和定义分开

方式一

定义放在同名的 .inl.tpp 文件中,同时在头文件的末尾使用 #include 包含进来,不可放到 .cpp 文件,否则会造成嵌套包含

// Test.h
#include <iostream>
using namespace std;
template<class T>
class Test
{
public:
    Test(T t){ m_t = t;};
    // explicit Test(T t){ m_t = t;};
    
    T getValue();
    
private:
    T m_t;
};
// 包含定义所在的文件
#include "Test.tpp"

// Test.tpp
template<typename T>
T Test<T>::getValue(){
    cout << typeid(m_t).name() << endl;
    return m_t;
};

// main.cpp
#include <iostream>
#include "Test.h"
using namespace std;

int main()
{
    Test<int> tInt(1);
    cout << tInt.getValue() << endl;

    Test<double> tD(1.15);
    cout << tD.getValue() << endl;
    
    Test<float> tF = 1.3f;                 // 构造函数没有explicit修饰,可以隐式转换
    cout << tF.getValue() << endl;

    return 0;
}

Output:
i
1
d
1.15
f
1.3

方式二

在头文件中声明实例化,并在一个源文件中定义它,但是只能使用已经显式声明的模板类实例

// Test.h
template<class T>
class Test
{
public:
    Test(T t){ m_t = t;};
    // explicit Test(T t){ m_t = t;};
    
    T getValue();
    
private:
    T m_t;
};
// 显式声明实例化
extern template class Test<int>;
typedef Test<float> TestF;
using TestD = Test<double>;

// Test.cpp
#include <iostream>
#include "Test.h"
using namespace std;

template<typename T>
T Test<T>::getValue(){
    cout << typeid(m_t).name() << endl;
    return m_t;
};
// 显式实例化
template class Test<int>;
template class Test<float>;
template class Test<double>;

// main.cpp
#include <iostream>
#include "Test.h"
using namespace std;

int main()
{
    Test<int> tInt(1);
    cout << tInt.getValue() << endl;

    Test<double> tD(1.15);
    // TestD tD(1.15);
    cout << tD.getValue() << endl;
    
    // Test<float> tF = 1.3f;
    TestF tF = 1.3f;                 // 构造函数没有explicit修饰,可以隐式转换
    cout << tF.getValue() << endl;

    return 0;
}

Output:
i
1
d
1.15
f
1.3

参考:

  1. c++模板声明和实现分离
  2. C++ 模板类的声明与实现分离问题(模板实例化)