在这里补充一些值得注意的内容。如果它们不是函数模板,那么可以在实现文件中定义模板类的方法。
myQueue.hpp:
template <class T>
class QueueA {
int size;
...
public:
template <class T> T dequeue() {
// implementation here
}
bool isEmpty();
...
}
myQueue.cpp:
// implementation of regular methods goes like this:
template <class T> bool QueueA<T>::isEmpty() {
return this->size == 0;
}
main()
{
QueueA<char> Q;
...
}
myQueue.cpp
之外,您不能从任何其他翻译单元调用isEmpty
函数。 - M.M如果担忧在将.h文件作为所有使用它的.cpp模块的一部分进行编译时所产生的额外编译时间和二进制文件大小的膨胀,很多情况下您可以使模板类从一个非模板化基类继承其接口的非类型相关部分,并且该基类可以在.cpp文件中实现。
template class X
的任何地方实现一个 class XBase
,将依赖于类型的部分放在 X
中,所有其余部分放在 XBase
中。 - Fabio A.template <typename T>
struct Foo
{
void doSomething(T param);
};
foo.tpp
#include "inner_foo.h"
template <typename T>
void Foo<T>::doSomething(T param)
{
//implementation
}
foo.h
#include <foo.tpp>
main.cpp
#include <foo.h>
inner_foo.h
包含了前向声明。 foo.tpp
包含了实现并引用了 inner_foo.h
, 而 foo.h
只需一行代码,将引用 foo.tpp
。
在编译时,foo.h
的内容会被复制到 foo.tpp
,然后整个文件会被复制到 foo.h
,之后进行编译。这种方式没有限制,并且命名一致,代价是多出一个文件。
我这样做是因为代码的静态分析器在 *.tpp
中看不到类的前向声明时会出错。这很烦人,在任何 IDE 或使用 YouCompleteMe 等工具编写代码时都会出现此问题。
这是完全正确的,因为编译器需要知道该分配哪种类型。因此,如果要将模板类、函数、枚举等作为公共或库的一部分(静态或动态),则必须在头文件中实现它们,因为头文件不像c/cpp文件那样被编译。如果编译器不知道类型,则无法编译它。在 .Net 中,它可以,因为所有对象都派生自 Object 类。但这不是 .Net。
https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html
"borland"模型对应于作者建议的完整模板定义,并且需要多次编译。它包含关于手动和自动模板实例化的明确建议。例如,可以使用“-repo”选项来收集需要实例化的模板。或者另一种选择是使用“-fno-implicit-templates”禁用自动模板实例化以强制手动模板实例化。main.cpp
#include "myclass.hpp"
int main()
{
// ...
}
myclass.hpp
#ifndef MYCLASS
#define MYCLASS
template<class T>
class MyClass
{
T val;
public:
MyClass(T val_);
}
#define MYCLASS_FUNCTIONS
#include "myclass.cpp"
#endif
myclass.cpp
#ifndef MYCLASS_FUNCTIONS
#include "myclass.hpp"
// regular functions:
// ...
#else
// template functions:
template<class T>
MyClass<T>::MyClass(T val_)
:val(val_)
{}
// ...
#endif
受到Moshe在https://dev59.com/O3RB5IYBdhLWcg3w1Kr0#38448106的回答的启发,我也做出了一点小贡献,提供一个扩展示例。假设有一个总体的OperationSuccess,其中包含一个具有通用类型的ResponseSuccess。
ResponseSuccess.h
template <class T>
class ResponseSuccess {
public:
ResponseSuccess(const ResponseStatus responseStatus, const T& data) :
m_responseStatus(responseStatus),
m_data(data) {}
~ResponseSuccess() = default;
// Basis requirement, have Copy/Move constructor/delete assignment operator
ResponseStatus getResponseStatus() const {
return m_responseStatus;
}
T getData() const {
return m_data;
};
private:
ResponseStatus m_responseStatus;
T m_data;
};
OperationSuccess.h
template <class T>
class OperationResponse {
public:
explicit OperationResponse(ResponseSuccess<T> responseSuccess) :
m_responseSuccess(std::move(responseSuccess)) {}
~OperationResponse() = default;
// Basis requirement, have Copy/Move constructor/delete assignment operator
ResponseSuccess<T> getResponseSuccess() const {
return m_responseSuccess;
}
private:
ResponseSuccess<T> m_responseSuccess;
// have a failure, in case required
};
使用方法:
MyObject myObj(<ctor_args>);
ResponseSuccess<MyObject> responseSuccess(ResponseStatus::SUCCESS, myObj);
OperationResponse<MyObject> successOperationResponse(responseSuccess);
..
// Fetches the response -> successOperationResponse.getResponseSuccess();
template <class T>
T min(T const& one, T const& theOther);
而在Utility.cpp中:
#include "Utility.h"
template <class T>
T min(T const& one, T const& other)
{
return one < other ? one : other;
}
这需要每个T类都实现小于运算符(<)。当您比较两个未实现“<”的类实例时,它将抛出编译器错误。
因此,如果您分离模板声明和定义,则无法仅读取头文件以查看此模板的内部细节,以便在自己的类上使用此API,尽管在这种情况下编译器会告诉您需要覆盖哪个运算符。
我必须编写一个模板类,这个例子对我很有帮助。
这是一个动态数组类的示例。
#ifndef dynarray_h
#define dynarray_h
#include <iostream>
template <class T>
class DynArray{
int capacity_;
int size_;
T* data;
public:
explicit DynArray(int size = 0, int capacity=2);
DynArray(const DynArray& d1);
~DynArray();
T& operator[]( const int index);
void operator=(const DynArray<T>& d1);
int size();
int capacity();
void clear();
void push_back(int n);
void pop_back();
T& at(const int n);
T& back();
T& front();
};
#include "dynarray.template" // this is how you get the header file
#endif
现在,在您的 .template 文件中,您可以像平常一样定义您的函数。
template <class T>
DynArray<T>::DynArray(int size, int capacity){
if (capacity >= size){
this->size_ = size;
this->capacity_ = capacity;
data = new T[capacity];
}
// for (int i = 0; i < size; ++i) {
// data[i] = 0;
// }
}
template <class T>
DynArray<T>::DynArray(const DynArray& d1){
//clear();
//delete [] data;
std::cout << "copy" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
}
template <class T>
DynArray<T>::~DynArray(){
delete [] data;
}
template <class T>
T& DynArray<T>::operator[]( const int index){
return at(index);
}
template <class T>
void DynArray<T>::operator=(const DynArray<T>& d1){
if (this->size() > 0) {
clear();
}
std::cout << "assign" << std::endl;
this->size_ = d1.size_;
this->capacity_ = d1.capacity_;
data = new T[capacity()];
for(int i = 0; i < size(); ++i){
data[i] = d1.data[i];
}
//delete [] d1.data;
}
template <class T>
int DynArray<T>::size(){
return size_;
}
template <class T>
int DynArray<T>::capacity(){
return capacity_;
}
template <class T>
void DynArray<T>::clear(){
for( int i = 0; i < size(); ++i){
data[i] = 0;
}
size_ = 0;
capacity_ = 2;
}
template <class T>
void DynArray<T>::push_back(int n){
if (size() >= capacity()) {
std::cout << "grow" << std::endl;
//redo the array
T* copy = new T[capacity_ + 40];
for (int i = 0; i < size(); ++i) {
copy[i] = data[i];
}
delete [] data;
data = new T[ capacity_ * 2];
for (int i = 0; i < capacity() * 2; ++i) {
data[i] = copy[i];
}
delete [] copy;
capacity_ *= 2;
}
data[size()] = n;
++size_;
}
template <class T>
void DynArray<T>::pop_back(){
data[size()-1] = 0;
--size_;
}
template <class T>
T& DynArray<T>::at(const int n){
if (n >= size()) {
throw std::runtime_error("invalid index");
}
return data[n];
}
template <class T>
T& DynArray<T>::back(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[size()-1];
}
template <class T>
T& DynArray<T>::front(){
if (size() == 0) {
throw std::runtime_error("vector is empty");
}
return data[0];
}