Solución de compilación separada para plantillas

revisar

Para las plantillas, como hemos dicho antes, las plantillas no admiten la compilación por separado (es decir, las declaraciones y las definiciones no están en el mismo archivo).

En la clase, sabemos que para las funciones con una cantidad de código relativamente pequeña, se reconocerán como funciones en línea de manera predeterminada para aumentar la eficiencia de la operación del código, mientras que algunas funciones con una cantidad de código relativamente grande aún se llamarán. 

Pero algunas funciones son realmente largas. Si todas estas funciones están escritas en la clase, es muy perjudicial para la legibilidad de nuestro código, por lo que el jefe tiene dos formas de mejorar estos problemas.

Análisis de causa

En primer lugar, debemos entender por qué la plantilla informará un error para la separación de declaración y definición.

De hecho, el informe es un error de enlace.

pd: Aquí hemos declarado y definido la separación

 test.obj: error LNK2019: símbolo externo no resuelto "public: void __cdecl fjz::vector<int>::push_back(int const &)" (?push_back@?$vector@H@fjz@@QEAAXAEBH@Z), el se hace referencia al símbolo en la función principal

 Este tipo de error generalmente se debe a que el compilador no encontró la definición de función correspondiente.

¿Pero por qué?  

¡Esto se debe a que las funciones definidas fuera de una clase no se pueden instanciar! !

Puede entender la clase de plantilla (función) como solo un molde. Este molde debe instanciarse de acuerdo con su parámetro de plantilla T..., y la función definida fuera de la clase no puede instanciarse junto con la instanciación de la clase porque no es en la clase. !

solución uno

Tome el vector como ejemplo

vector.h

template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;

	void reserve(size_t n);
	void push_back(const T& val);
	iterator erase(iterator pos);
	iterator erase(iterator first, iterator last);

public:
	iterator _start;
	iterator _finish;
	iterator _end_of_storage;
};

Aquí he omitido muchas funciones cortas que no necesitan ser declaradas y definidas por separado.

vectorcor.cpp

#include"vector.h"
template<class T>
void vector<T>::reserve(size_t n)
{
	if (capacity() < n)   //判断是否需要扩容
	{
		size_t sz = size();   //保存size的数据
		T* tmp = new T[n];
		if (_start)           //如果_start不为空指针,则进行拷贝和delete
		{
			for (size_t i = 0; i < sz; i++)
			{
				tmp[i] = _start[i];   //调用赋值来完成深拷贝
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

template<class T>
void vector<T>::push_back(const T& val)
{
	if (_finish == _end_of_storage)
	{
		reserve(empty() ? 4 : capacity() * 2);  //如果数据为空,则给初始空间为4
	}
	*_finish = val;
	_finish++;
}

template<class T>
typename vector<T>::iterator vector<T>::erase(typename vector<T>::iterator pos)
{
	assert(pos < _finish);
	for (int i = 0; i < _finish - pos - 1; i++)
	{
		pos[i] = pos[i + 1];
	}
	--_finish;
	return pos;
}

template<class T>
typename vector<T>::iterator vector<T>::erase(typename vector<T>::iterator first,
                                                       typename vector<T>::iterator last)
{
	assert(last <= _finish);
	assert(first <= _finish);
	size_t len = last - first;
	for (int i = 0; i < _finish - last; i++)
	{
		first[i] = last[i];
	}
	_finish -= len;
	return first;
}

Mire cuidadosamente el formato de la definición de separación. Para el iterador, este es el tipo que redefinimos en la clase. Debe declararse como un tipo en vector, de lo contrario, ¡no compilará!

En la actualidad, este ya es un formato estándar para la separación de declaración y definición, si llamamos ahora a funciones relacionadas con vectores, aparecerá el problema de enlace que acabamos de mencionar anteriormente, las funciones definidas en .cpp no ​​se pueden instanciar.

Entonces, ¿cuál es la solución?

¡Déjalo instanciar!

#include"vector.h"
template<class T>
void vector<T>::reserve(size_t n)
{
	if (capacity() < n)   //判断是否需要扩容
	{
		size_t sz = size();   //保存size的数据
		T* tmp = new T[n];
		if (_start)           //如果_start不为空指针,则进行拷贝和delete
		{
			for (size_t i = 0; i < sz; i++)
			{
				tmp[i] = _start[i];   //调用赋值来完成深拷贝
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

template<class T>
void vector<T>::push_back(const T& val)
{
	if (_finish == _end_of_storage)
	{
		reserve(empty() ? 4 : capacity() * 2);  //如果数据为空,则给初始空间为4
	}
	*_finish = val;
	_finish++;
}

template<class T>
typename vector<T>::iterator vector<T>::erase(typename vector<T>::iterator pos)
{
	assert(pos < _finish);
	for (int i = 0; i < _finish - pos - 1; i++)
	{
		pos[i] = pos[i + 1];
	}
	--_finish;
	return pos;
}

template<class T>
typename vector<T>::iterator vector<T>::erase(typename vector<T>::iterator first,
                                                       typename vector<T>::iterator last)
{
	assert(last <= _finish);
	assert(first <= _finish);
	size_t len = last - first;
	for (int i = 0; i < _finish - last; i++)
	{
		first[i] = last[i];
	}
	_finish -= len;
	return first;
}

template
vector<int>;   //实例化vector<int>

template
vector<double>;   //实例化vector<double>

De esta manera, se puede instanciar, pero el defecto de este método también es obvio. Debe agregar los tipos que se deben instanciar en el archivo .cpp de antemano. Es mejor no compilar diferentes archivos por separado.

El segundo método que se describe a continuación también es un método más recomendado.

solución dos

También es la mejor manera de separar la declaración y la definición en el mismo archivo, y STL también adopta este método.

template<class T>
class vector
{
public:
	typedef T* iterator;
	typedef const T* const_iterator;

	void reserve(size_t n);
	void push_back(const T& val);
	iterator erase(iterator pos);
	iterator erase(iterator first, iterator last);

public:
	iterator _start;
	iterator _finish;
	iterator _end_of_storage;
};

template<class T>
void vector<T>::reserve(size_t n)
{
	if (capacity() < n)   //判断是否需要扩容
	{
		size_t sz = size();   //保存size的数据
		T* tmp = new T[n];
		if (_start)           //如果_start不为空指针,则进行拷贝和delete
		{
			for (size_t i = 0; i < sz; i++)
			{
				tmp[i] = _start[i];   //调用赋值来完成深拷贝
			}
			delete[] _start;
		}
		_start = tmp;
		_finish = _start + sz;
		_end_of_storage = _start + n;
	}
}

template<class T>
void vector<T>::push_back(const T& val)
{
	if (_finish == _end_of_storage)
	{
		reserve(empty() ? 4 : capacity() * 2);  //如果数据为空,则给初始空间为4
	}
	*_finish = val;
	_finish++;
}

template<class T>
typename vector<T>::iterator vector<T>::erase(typename vector<T>::iterator pos)
{
	assert(pos < _finish);
	for (int i = 0; i < _finish - pos - 1; i++)
	{
		pos[i] = pos[i + 1];
	}
	--_finish;
	return pos;
}

template<class T>
typename vector<T>::iterator vector<T>::erase(typename vector<T>::iterator first, typename vector<T>::iterator last)
{
	assert(last <= _finish);
	assert(first <= _finish);
	size_t len = last - first;
	for (int i = 0; i < _finish - last; i++)
	{
		first[i] = last[i];
	}
	_finish -= len;
	return first;
}

Supongo que te gusta

Origin blog.csdn.net/fengjunziya/article/details/130946360
Recomendado
Clasificación