在程序实现过程中,发现类模板的声明和定义必须放在同一个头文件中,如果分开将声明放在头文件Stack.h中,定义实现放在Stack.cpp文件中,则运行主程序会报错error LNK2019: 无法解析的外部符号 "public: __thiscall Stack<int>::Stack<int>(void)" (??0?$Stack@H@@QAE@XZ),该符号在函数 _main 中被引用
这种错误的解释:
当将类模板声明放在头文件Stack.h中,定义实现放在Stack.cpp文件中,最后在另外一个.cpp文件中使用这个模板,并且将模板声明头文件包含进这个文件。我们在主函数中执行intStack.push(7) ,程序运行后,链接器报错,如上。这个错误的原因在于:类模板成员函数push()的定义还没有被实例化。为了使模板真正得到实例化,编译器必须知道:应该实例化哪个定义以及要基于哪个模板实参来进行实例化。遗憾的是,这两部分信息位于分开编译的不同文件里面。因此,当我们的编译器看到push()调用,但还没有看到基于int实例化的函数定义的时候,它只假设在别处提供了这个定义,并产生一个指向该定义的引用(让链接器利用该引用来解决这个问题)。另一方面,当编译器处理文件Stack.cpp的时候,它并没有指出:编译器必须基于特定实参对所包含的模板定义进行实例化。
第一种方法:使用包含模型
对于这个问题,我们通常是采取对待宏或内联函数的解决办法,我们把模板的定义也包含在声明模板的头文件里面,所以这里我们在同一个头文件中声明和定义类Stack<>。我们称模板的这种组织方式为包含模型。
1、类模板Stack的实现Stack.h文件
//这里我们在同一个头文件中声明和定义类Stack<>
#include<stdio.h>
#include<vector>
#include<iostream>
using namespace std;
//类声明
template<typename T>
class Stack{
private:
vector<T> elements; //存储元素的容器
public:
//Stack();//构造函数 这里不能加这个构造函数,而是使用默认构造函数,加了会报错error LNK2019: 无法解析的外部符号 "public: __thiscall Stack<int>::Stack<int>(void)" (??0?$Stack@H@@QAE@XZ),该符号在函数 _main 中被引用
void push(T const&);//压入元素
void pop();//弹出元素
T top() const;//返回栈顶元素
bool empty() const{ //返回栈是否为空
return elements.empty();
}
//Stack(Stack<T> const&);//拷贝构造函数 ,当没有自己声明构造函数时,使用拷贝构造函数会报错error C2512: “Stack<T>”: 没有合适的默认构造函数可用
Stack<T>& operator=(Stack<T> const&); //赋值运算符
};
//类成员函数的实现(也可以将下面成员函数作为内联函数,将它们定义于类声明里面,就像empty()的实现一样)
template<typename T>
void Stack<T>::push(T const& e_em){
elements.push_back(e_em);//
}
/***
因为当vector为空时,它的back方法(返回末端元素的值)和pop_back()方法会具有未加定义色行为,
因此在pop()实现中我们需要先检查该stack是否为空。如果为空,就抛出out_of_range异常。
同样在top()实现中,也这样判断。
****/
template<typename T>
void Stack<T>::pop(){
if (elements.empty())
{
throw out_of_range("Stack<T>::pop(): empty stack");
}
elements.pop_back();//删除最后一个元素
}
template<typename T>
T Stack<T>::top() const{
if(elements.empty()){
throw out_of_range("Stack<T>::pop(): empty stack");
}
return elements.back();//返回最后一个元素的拷贝
}
2、类模板的使用stacktest1.cpp
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#include<vector>
#include<string>
#include"Stack.h"
using namespace std;
//类模板Stack的使用
int main(){
try
{
Stack<int> intStack; //元素类型为int的栈
Stack<string> stringStack; //元素类型为string的栈
//使用int栈
intStack.push(7);
cout<<intStack.top()<<endl;
//使用string栈
stringStack.push("hello");
cout<<stringStack.top()<<endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const & ex)
{
cerr<<"Exception: "<<ex.what()<<endl;
//return EXIT_FAILURE; //程序退出,且带有ERROR标记
}
system("pause");
}
2、整合包含模型和显式实例化
为了让程序员能够根据实际情况,自由地选择包含模型或者显式实例化,我们可以把模板得定义和声明放在两个不同的头文件中。通常的做法是在Stack.h文件中写模板声明,在另外一个头文件Stackdef.h中写模板定义,并在头文件Stackdef.h中要包含进Stack.h文件。如下,分开模板声明和模板定义
1)、Stack.h文件
#ifndef STACK_H
#define STACK_H
//这里我们在同一个头文件中声明类Stack<>
#include<vector>
#include<iostream>
using namespace std;
//类声明
template<typename T>
class Stack{
private:
vector<T> elements; //存储元素的容器
public:
//Stack();//构造函数 这里不能加这个构造函数,而是使用默认构造函数,加了会报错error LNK2019: 无法解析的外部符号 "public: __thiscall Stack<int>::Stack<int>(void)" (??0?$Stack@H@@QAE@XZ),该符号在函数 _main 中被引用
void push(T const&);//压入元素
void pop();//弹出元素
T top() const;//返回栈顶元素
bool empty() const{ //返回栈是否为空
return elements.empty();
}
//Stack(Stack<T> const&);//拷贝构造函数 ,当没有自己声明构造函数时,使用拷贝构造函数会报错error C2512: “Stack<T>”: 没有合适的默认构造函数可用
Stack<T>& operator=(Stack<T> const&); //赋值运算符
};
#endif<span style="color:#3333ff;">
</span>
2)、Stackdef.h中定义类模板
#ifndef STACKDEF_H
#define STACKDEF_H
#include"Stack.h"//包含进类声明的头文件
#include<vector>
#include<iostream>
using namespace std;
//类成员函数的实现(也可以将下面成员函数作为内联函数,将它们定义于类声明里面,就像empty()的实现一样)
template<typename T>
void Stack<T>::push(T const& e_em){
elements.push_back(e_em);//
}
/***
因为当vector为空时,它的back方法(返回末端元素的值)和pop_back()方法会具有未加定义色行为,
因此在pop()实现中我们需要先检查该stack是否为空。如果为空,就抛出out_of_range异常。
同样在top()实现中,也这样判断。
****/
template<typename T>
void Stack<T>::pop(){
if (elements.empty())
{
throw out_of_range("Stack<T>::pop(): empty stack");
}
elements.pop_back();//删除最后一个元素
}
template<typename T>
T Stack<T>::top() const{
if(elements.empty()){
throw out_of_range("Stack<T>::pop(): empty stack");
}
return elements.back();//返回最后一个元素的拷贝
}
#endif
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#include<vector>
#include<string>
#include"Stackdef.h"
using namespace std;
//类模板Stack的使用
int main(){
try
{
Stack<int> intStack; //元素类型为int的栈
Stack<string> stringStack; //元素类型为string的栈
//使用int栈
intStack.push(7);
cout<<intStack.top()<<endl;
//使用string栈
stringStack.push("hello");
cout<<stringStack.top()<<endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const & ex)
{
cerr<<"Exception: "<<ex.what()<<endl;
//return EXIT_FAILURE; //程序退出,且带有ERROR标记
}
system("pause");
}
另外如果我们希望实例化模板,我们就应该在主文件中包含进类声明Stack.h文件,然后提供一个含有所需要实例化指示符的c文件Stack_inst.cpp,这样即可。下面即是在拥有两个模板头文件的情况下,进行显式实例化。
主程序文件stacktest1.cpp
#include<stdio.h>
#include<iostream>
#include<stdlib.h>
#include<vector>
#include<string>
#include"Stack.h"//应该包含类模板声明头文件
using namespace std;
//类模板Stack的使用
int main(){
try
{
Stack<int> intStack; //元素类型为int的栈
Stack<string> stringStack; //元素类型为string的栈
//使用int栈
intStack.push(7);
cout<<intStack.top()<<endl;
//使用string栈
stringStack.push("hello");
cout<<stringStack.top()<<endl;
stringStack.pop();
stringStack.pop();
}
catch (exception const & ex)
{
cerr<<"Exception: "<<ex.what()<<endl;
//return EXIT_FAILURE; //程序退出,且带有ERROR标记
}
system("pause");
}
显式实例化程序文件Stack_inst.cpp
#include"Stackdef.h"//包含类模板定义头文件
#include<string>
#include<iostream>
using namespace std;
//实例化类Stack<> for intss
template Stack<int>;
//实例化some member function of Stack<> for strings
template void Stack<string>::push(string const&);
template string Stack<string>::top() const;
template void Stack<string>::pop();