【C++】关于C++类模板不支持分离编译的一些知识点

引入

我们知道在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++类模板不支持分离编译的一些知识,如有不足或遗漏之处还请大家指正,笔者感激不尽;同时也欢迎大家在评论区进行讨论,一起学习,共同进步!

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值