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

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

这是为什么?

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


当前回答

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

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

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

其他回答

在头文件中编写声明和定义是一个好主意的另一个原因是为了可读性。假设Utility.h中有这样一个模板函数:

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,尽管在这种情况下编译器会告诉您需要重写哪个运算符。

这是因为需要单独编译,并且模板是实例化样式多态性。

让我们更接近具体的解释。假设我有以下文件:

小时声明类MyClass<T>的接口foo.cpp定义类MyClass<T>的实现棒.cpp使用MyClass<int>

独立编译意味着我应该能够独立于bar.cpp编译foo.cpp。编译器完全独立地在每个编译单元上完成分析、优化和代码生成的所有艰苦工作;我们不需要做整个程序分析。只有链接器需要一次处理整个程序,而且链接器的工作要简单得多。

当我编译foo.cpp时,bar.cpp甚至不需要存在,但我应该仍然能够将我已经拥有的foo.o与我刚刚制作的bar.o链接在一起,而不需要重新编译foo.cpp。foo.cpp甚至可以被编译成一个动态库,在没有foo.cpp的情况下分发到其他地方,并与他们在我编写foo.cpp多年后编写的代码链接。

“实例化样式多态性”意味着模板MyClass<T>实际上不是一个通用类,它可以编译为可用于任何T值的代码。这会增加诸如装箱之类的开销,需要将函数指针传递给分配器和构造函数等。C++模板的目的是避免编写几乎相同的类MyClass_int、类MyClass_float等,但最终仍然能够编写出编译后的代码,就像我们分别编写了每个版本一样。因此,模板实际上就是模板;类模板不是类,它是为我们遇到的每个T创建新类的方法。模板不能编译成代码,只能编译实例化模板的结果。

因此,当编译foo.cpp时,编译器无法看到bar.cpp以知道需要MyClass<int>。它可以看到模板MyClass<T>,但不能为此发出代码(它是一个模板,而不是类)。编译bar.cpp时,编译器可以看到它需要创建MyClass<int>,但它看不到模板MyClass<t>(只有foo.h中的接口),因此无法创建它。

如果foo.cpp本身使用MyClass<int>,那么编译foo.cpp时将生成该代码,因此当bar.o链接到foo.o时,它们可以连接起来并工作。我们可以使用这个事实,通过编写一个模板,在.cpp文件中实现一组有限的模板实例化。但bar.cpp无法将模板用作模板,并在它喜欢的任何类型上实例化它;它只能使用foo.cpp作者认为可以提供的模板类的预先存在的版本。

您可能会认为,在编译模板时,编译器应该“生成所有版本”,在链接过程中过滤掉从未使用过的版本。除了由于指针和数组等“类型修改器”特性,即使是内置类型也可以产生无限数量的类型,因此这种方法将面临巨大的开销和极端困难外,现在我通过添加以下内容来扩展程序时会发生什么:

巴兹卡普声明并实现类BazPrivate,并使用MyClass<BazPrivate>

除非我们

每次我们更改程序中的任何其他文件时,都必须重新编译foo.cpp,以防它添加了MyClass的新实例<T>要求baz.cpp包含(可能通过headerincludes)MyClass<T>的完整模板,以便编译器可以在编译baz.cpp期间生成MyClass<BazPrivate>。

没有人喜欢(1),因为整个程序分析编译系统需要很长时间才能编译,而且如果没有源代码,就无法分发编译后的库。所以我们有(2)。

实际上,在C++11之前,该标准定义了export关键字,它可以在头文件中声明模板并在其他地方实现它们。在某种程度上讲。并非如此,正如唯一实现过该功能的人所指出的:

幻影优势#1:隐藏源代码。许多用户表示,他们希望通过使用导出不再需要为类的成员/非成员函数模板和成员函数提供定义模板。这不是真的。在导出的情况下,库编写者仍然必须提供完整的模板源代码或其直接因为实例化需要完整的信息。[...]

幻影优势#2:快速构建,减少依赖性。许多用户希望导出将允许真正的分离将模板编译为目标代码,他们希望这样可以加快构建速度。这不是因为导出模板的编译确实是独立的,但与目标代码无关。相反,出口几乎总是构建速度较慢,因为至少在预链接时仍必须完成相同数量的编译工作。出口甚至不减少模板定义之间的依赖性,因为依赖性是内在的,独立于文件组织。

没有一个流行的编译器实现了这个关键字。该功能的唯一实现是由Edison Design Group编写的前端,由Comeau C++编译器使用。所有其他人都要求您在头文件中编写模板,因为编译器需要模板定义来进行适当的实例化(正如其他人已经指出的那样)。

因此,ISO C++标准委员会决定删除C++11模板的导出功能。

注意:没有必要将实现放在头文件中,请参阅答案末尾的替代解决方案。

无论如何,代码失败的原因是,在实例化模板时,编译器使用给定的模板参数创建一个新类。例如:

template<typename T>
struct Foo
{
    T bar;
    void doSomething(T param) {/* do stuff using T */}
};

// somewhere in a .cpp
Foo<int> f; 

当读取这一行时,编译器将创建一个新类(让我们称之为FooInt),这相当于以下内容:

struct FooInt
{
    int bar;
    void doSomething(int param) {/* do stuff using int */}
}

因此,编译器需要访问方法的实现,以使用模板参数(在本例中为int)实例化它们。如果这些实现不在标头中,那么它们将无法访问,因此编译器将无法实例化模板。

对此的常见解决方案是在头文件中写入模板声明,然后在实现文件(例如.tpp)中实现类,并在头的末尾包含该实现文件。

食品h

template <typename T>
struct Foo
{
    void doSomething(T param);
};

#include "Foo.tpp"

食品.tpp

template <typename T>
void Foo<T>::doSomething(T param)
{
    //implementation
}

这样,实现仍然与声明分离,但编译器可以访问。

替代解决方案

另一个解决方案是保持实现分离,并显式实例化您需要的所有模板实例:

食品h

// no implementation
template <typename T> struct Foo { ... };

食品.cpp

// implementation of Foo's methods

// explicit instantiations
template class Foo<int>;
template class Foo<float>;
// You will only be able to use Foo with int or float

如果我的解释不够清楚,你可以看看这个主题的C++超级常见问题解答。

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