C++ 模板类声明和实现遇到的问题

声明一个 模板类的头文件 Stack.h

#include <vector>
#include <cassert>

template<typename T>
class Stack {
private:
    std::vector<T> mystack;
public:
    void push(T const  &elem);

    void pop();

    T const &top() const;

    bool empty() const;
};

另外声明一个模板类的实现文件 Stack.cpp

#include "Stack.h"

template<typename T>
void Stack<T>::push(T const &elem) {
    mystack.push_back(elem);
}

template<typename T>
void Stack<T>::pop() {
    assert(!mystack.empty());
    mystack.pop_back();
}

template<typename T>
T const &Stack<T>::top() const {
    assert(!mystack.empty());
    return mystack.front();
}

template<typename T>
bool Stack<T>::empty() const {
    return mystack.empty();
}

在 main 里使用这个模板类

#include <iostream>
#include "Stack.h"

int main() {
    Stack<int> mystack;
    std::cout << mystack.empty();
    mystack.push(10);
    int a = mystack.top();
    mystack.pop();
    std::cout << mystack.empty();
    mystack.pop();
    return 0;
}

编译正常, 链接的时候报如下错误:

main.cpp:6: undefined reference to `Stack<int>::empty() const'
main.cpp:7: undefined reference to `Stack<int>::push(int const&)'
main.cpp:8: undefined reference to `Stack<int>::top() const'
main.cpp:9: undefined reference to `Stack<int>::pop()'
main.cpp:10: undefined reference to `Stack<int>::empty() const'
main.cpp:11: undefined reference to `Stack<int>::pop()'

分析模板实例化的过程:

  编译器使用模板,通过更换模板参数来创建数据类型。这个过程就是模板实例化(Instantiation)。
  从模板类创建得到的类型称之为特例(specialization)。 
  模板实例化取决于编译器能够找到可用代码来创建特例(称之为实例化要素,point of instantiation)。
  要创建特例,编译器不但要看到模板的声明,还要看到模板的定义。
  模板实例化过程是迟钝的,即只能用函数的定义来实现实例化。

再回头看上面的例子,可以知道Stack是一个模板,Stack<int>是一个模板实例 - 一个类型。从Stack创建Stack<int>的过程就是实例化过程。实例化要素体现在main.cpp文件中。如果按照传统方式,编译器在Stack.h文件中看到了模板的声明,但没有 模板的定义,这样编译器就不能创建类型Stack<int>。但这时并不出错,因为编译器认为模板定义在其它文件中,就把问题留给链接程序处理。

现在,编译Stack.cpp时会发生什么问题呢?编译器可以解析模板定义并检查语法,但不能生成成员函数的代码。它无法生成代码,因为要生成代码,需要知道模板参数,即需要一个类型,而不是模板本身。

这样,链接程序在main.cpp 或 array.cpp中都找不到Stack<int>的定义,于是报出无定义成员的错误。

解决方法:

第一种方法意味着在使用模板的转换文件中不但要包含模板声明文件,还要包含模板定义文件。这样编译器就能看到模板的声明和定义,并由此生成 Stack<int>实例。这样做的缺点是编译文件会变得很大,显然要降低编译和链接速度。

第二种方法,通过显式的模板实例化得到类型。最好将所有的显式实例化过程安放在另外的文件中。在本例中,可以创建一个新文件templateinstantiations.cpp:      
#include "Stack.cpp"

template class Stack<int>; // 显式实例化
        
Stack<int>类型不是在main.cpp中产生,而是在templateinstantiations.cpp中产生。这样链接器就能够找到它的定义。用 这种方法,不会产生巨大的头文件,加快编译速度。而且头文件本身也显得更加“干净”和更具有可读性。但这个方法不能得到惰性实例化的好处,即它将显式地生 成所有的成员函数。另外还要维护templateinstantiations.cpp文件。

第三种方法是在模板定义中使用export关键字,剩下的事就让编译器去自行处理了。

Reference:

1.https://www.cnblogs.com/dracohan/p/3402041.html

2.https://stackoverflow.com/questions/1724036/splitting-templated-c-classes-into-hpp-cpp-files-is-it-possible

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值