引用C++标准库:教程和手册:

目前使用模板的唯一可移植方式是使用内联函数在头文件中实现它们。

这是为什么?

(澄清:头文件不是唯一的可移植解决方案。但它们是最方便的可移植方案。)


当前回答

这是完全正确的,因为编译器必须知道它的分配类型。模板类、函数、枚举等。。如果要将头文件公开或作为库的一部分(静态或动态),则也必须在头文件中实现,因为头文件不像c/cpp文件那样编译。如果编译器不知道该类型,则无法编译它。在.Net中,它可以编译,因为所有对象都派生自Object类。这不是.Net。

其他回答

这是完全正确的,因为编译器必须知道它的分配类型。模板类、函数、枚举等。。如果要将头文件公开或作为库的一部分(静态或动态),则也必须在头文件中实现,因为头文件不像c/cpp文件那样编译。如果编译器不知道该类型,则无法编译它。在.Net中,它可以编译,因为所有对象都派生自Object类。这不是.Net。

我必须写一个模板类,这个例子对我很有用

下面是一个动态数组类的示例。

#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];
    }

这里有很多正确的答案,但我想补充一下(为了完整):

如果在实现cpp文件的底部,对模板将使用的所有类型进行显式实例化,链接器将能够像往常一样找到它们。

编辑:添加显式模板实例化示例。在定义模板并定义所有成员函数后使用。

template class vector<int>;

这将实例化(从而使链接器可用)类及其所有成员函数(仅限)。类似的语法适用于函数模板,因此如果您有非成员运算符重载,则可能需要对这些重载执行相同的操作。

上面的例子是非常无用的,因为vector是在头文件中完全定义的,除非公共include文件(预编译头文件?)使用外部模板类vector<int>,以防止它在所有其他使用vector的(1000?)文件中实例化。

当您在编译步骤中使用模板时,编译器将为每个模板实例化生成代码。在编译和链接过程中,.cpp文件被转换为纯对象或机器代码,其中包含引用或未定义的符号,因为main.cpp中包含的.h文件没有实现YET。这些文件已准备好与另一个对象文件链接,该对象文件定义了模板的实现,因此您拥有完整的a.out可执行文件。

然而,由于模板需要在编译步骤中进行处理,以便为您定义的每个模板实例化生成代码,因此简单地编译一个与它的头文件分离的模板是行不通的,因为它们总是并行的,因为每个模板实例化都是一个全新的类。在常规类中,您可以将.h和.cpp分开,因为.h是该类的蓝图,.cpp是原始实现,因此任何实现文件都可以定期编译和链接,但是使用模板.h是类外观的蓝图,而不是对象外观,这意味着模板.cpp文件不是类的原始常规实现,它只是一个类的蓝图,因此任何.h模板文件的实现都无法编译,因为您需要一些具体的东西来编译,在这个意义上,模板是抽象的。

因此,模板永远不会单独编译,只在其他源文件中有具体实例化的地方编译。然而,具体的实例化需要知道模板文件的实现,因为简单地使用.h文件中的具体类型修改类型名T并不能完成任务,因为.cpp要链接什么,我以后找不到它,因为记住模板是抽象的,无法编译,所以我现在不得不给出实现,这样我就知道要编译和链接什么,现在我有了实现,它就链接到了封闭的源文件中。基本上,当我实例化一个模板时,我需要创建一个全新的类,如果我不知道该类在使用我提供的类型时应该是什么样子,我就不能这样做,除非我通知编译器模板实现,所以现在编译器可以用我的类型替换t,并创建一个可以编译和链接的具体类。

总之,模板是类外观的蓝图,类是对象外观的蓝图。我不能将模板与它们的具体实例化分开编译,因为编译器只编译具体类型,换句话说,至少在C++中,模板是纯语言抽象的。可以说,我们必须对模板进行去抽象,我们通过给它们一个具体的类型来处理,这样我们的模板抽象就可以转换为一个常规的类文件,进而可以正常编译。分隔template.h文件和template.cpp文件是没有意义的。这是没有意义的,因为.cpp和.h的分离仅限于.cpp可以单独编译和单独链接的地方,因为我们不能单独编译它们,因为模板是一个抽象,因此,我们总是被迫将抽象与具体实例化放在一起,其中具体实例化总是必须知道所使用的类型。

这意味着typename T get在编译步骤而不是链接步骤中被替换,所以如果我试图编译一个模板而不将T替换为一个对编译器来说完全没有意义的具体值类型,结果无法创建对象代码,因为它不知道T是什么。

从技术上讲,可以创建某种功能来保存template.cpp文件,并在其他源中找到它们时切换类型。

附带说明一下,当为模板类进行专门化时,可以将头与实现分开,因为根据定义,专门化意味着我专门化了一个可以单独编译和链接的具体类型。

尽管上面有很多很好的解释,但我缺少一种将模板分离为页眉和正文的实用方法。

我主要担心的是,当我更改其定义时,避免重新编译所有模板用户。

对我来说,在模板主体中包含所有模板实例不是一个可行的解决方案,因为模板作者可能不知道其使用情况,模板用户可能无权修改它。

我采用了以下方法,这也适用于较旧的编译器(gcc 4.3.4,aCC A.03.13)。

对于每个模板的使用,在其自己的头文件(从UML模型生成)中都有一个typedef。它的主体包含实例化(最终在一个库中,该库在末尾链接)。

模板的每个用户都包含该头文件并使用typedef。

示意图示例:

MyTemplate.h:

#ifndef MyTemplate_h
#define MyTemplate_h 1

template <class T>
class MyTemplate
{
public:
  MyTemplate(const T& rt);
  void dump();
  T t;
};

#endif

我的模板.cpp:

#include "MyTemplate.h"
#include <iostream>

template <class T>
MyTemplate<T>::MyTemplate(const T& rt)
: t(rt)
{
}

template <class T>
void MyTemplate<T>::dump()
{
  cerr << t << endl;
}

MyInstantiatedTemplate.h:

#ifndef MyInstantiatedTemplate_h
#define MyInstantiatedTemplate_h 1
#include "MyTemplate.h"

typedef MyTemplate< int > MyInstantiatedTemplate;

#endif

我的实例化模板.cpp:

#include "MyTemplate.cpp"

template class MyTemplate< int >;

main.cpp:

#include "MyInstantiatedTemplate.h"

int main()
{
  MyInstantiatedTemplate m(100);
  m.dump();
  return 0;
}

这样,只需要重新编译模板实例,而不是所有模板用户(和依赖项)。