引入
我们知道在C++中类方法的声明和定义是可以分开进行的,即在类内进行声明,将类放置于.h文件中,然后将方法的定义放在类名.cpp文件中实现定义与声明分离。这样做一般包含两个好处,第一个是可以不暴露源码,将实现方式隐藏起来;另一个就是如果类内方法过多,将声明和定义分离后浏览类方法时会更加清晰明了,减少了读代码的代价。但是在类模板中,我们将类模板中的方法进行声明和定义分离时会出现一些问题。
问题提出
我们先将分好的三个文件的代码放在这里:
//Stack.cpp
# define _CRT_SECURE_NO_WARNINGS 1
# include "Stack.h"
template<class T>
Stack<T>::Stack(int capacity)
{
std::cout << "Stack(int capacity = )" << capacity << std::endl;
_a = (T*)malloc(sizeof(T) * capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
}
template<class T>
Stack<T>::~Stack()
{
std::cout << "~Stack()" << std::endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
template<class T>
void Stack<T>::Push(const T& x)
{
// ....
// 扩容
_a[_top++] = x;
}
//Stack.h
#pragma once
#include <iostream>
using namespace std;
template<typename T>
class Stack
{
public:
Stack(int capacity = 4);
~Stack();
void Push(const T& x);
private:
T* _a;
int _top;
int _capacity;
};
//main.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
int main()
{
Stack<int> st;
st.Push(1);
st.Push(2);
st.Push(3);
return 0;
}
当我们运行代码时,编译器会报出以下错误:
可以清晰地看到,这里报出的四个错误均为链接过程中出现的错误,错误对应的文件也是后缀为obj的文件,说明编译过程并没有出现问题,而是链接过程出现了问题。
问题剖析
我们知道,在进行多文件编程时,在编译结束后要对各个cpp文件进行链接,其中一个关键步骤就是将声明和定义分离的函数进行链接,在此过程中需要到对应的obj文件的符号表中找到相应函数的定义内容,而问题,正出现在这里。
正常来说普通的类方法声明和定义分离后,在编译过程中会针对类方法的定义生成地址,以便后续链接过程中找到此方法,但生成地址的前提是这个函数是一个具体的函数,而不能是一个还没有实例化的模板。一个没有实例化的模板是不能够被直接调用的,因此它也没有机会进入符号表,链接时自然也就查无此“人”。所以我们在主函数文件中进行了实例化,并包含了Stack.h文件,但在这个文件中我们仅仅对类方法进行了声明,没有进行定义;而我们真正进行定义的Stack.cpp处却没有进行实例化,正因如此,我们在进行链接时就找不到对应的方法,也就报出了链接错误。
问题解决
方法一:在Stack.cpp文件中进行手动的显式实例化
代码如下:
//Stack.cpp
# define _CRT_SECURE_NO_WARNINGS 1
# include "Stack.h"
template<class T>
Stack<T>::Stack(int capacity)
{
std::cout << "Stack(int capacity = )" << capacity << std::endl;
_a = (T*)malloc(sizeof(T) * capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
}
template<class T>
Stack<T>::~Stack()
{
std::cout << "~Stack()" << std::endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
template<class T>
void Stack<T>::Push(const T& x)
{
// ....
// 扩容
_a[_top++] = x;
}
template class Stack<int>;
但是这种解决方法可以用饮鸩止渴来形容,因为这里我们要生成的是整型的栈,如果我们想生成字符类型、浮点型的栈抑或是某种自定义类型的栈,那么我们就要针对每一种都在Stack.cpp中进行手动实例化,这样是没法从根本上解决问题的。所以正经的解决方法是:
方法二:对于这些类方法我们不进行定义和声明分离
这里我们选择直接取消掉Stack.cpp,将定义也放在头文件中,这样在预处理阶段就直接就放到了主函数前面,在主函数中实例化后自然也就不会出现链接问题。
代码如下:
//Stack.h
#pragma once
#include <iostream>
using namespace std;
template<typename T>
class Stack
{
public:
Stack(int capacity = 4)
{
cout << "Stack(int capacity = )" <<capacity<<endl;
_a = (T*)malloc(sizeof(T)*capacity);
if (_a == nullptr)
{
perror("malloc fail");
exit(-1);
}
_top = 0;
_capacity = capacity;
}
~Stack()
{
cout << "~Stack()" << endl;
free(_a);
_a = nullptr;
_top = _capacity = 0;
}
void Push(const T& x)
{
// ....
// 扩容
_a[_top++] = x;
}
private:
T* _a;
int _top;
int _capacity;
};
//main.cpp
#define _CRT_SECURE_NO_WARNINGS 1
#include "Stack.h"
int main()
{
Stack<int> st;
st.Push(1);
st.Push(2);
st.Push(3);
return 0;
}
结束语
以上就是关于C++类模板不支持分离编译的一些知识,如有不足或遗漏之处还请大家指正,笔者感激不尽;同时也欢迎大家在评论区进行讨论,一起学习,共同进步!