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

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

这是为什么?

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


当前回答

如果问题是将.h编译为使用它的所有.cpp模块的一部分所产生的额外编译时间和二进制大小膨胀,那么在许多情况下,您可以做的是使模板类从接口的非类型依赖部分的非模板化基类下降,并且该基类可以在.cpp文件中实现。

其他回答

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

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

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

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

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

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

我采用了以下方法,这也适用于较旧的编译器(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;
}

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

在实际将模板编译成目标代码之前,需要由编译器实例化模板。只有当模板参数已知时,才能实现此实例化。现在想象一个场景,其中模板函数在a.h中声明,在a.pp中定义,并在b.cpp中使用。编译a.pp时,不一定知道即将到来的编译b.cpp将需要模板的实例,更不用说是哪个特定的实例。对于更多的头文件和源文件,情况可能会很快变得更加复杂。

有人会说,编译器可以更聪明地“前瞻”模板的所有用途,但我确信创建递归或其他复杂场景并不困难。顺便说一句,编译器不会再这样做了。正如Anton所指出的,一些编译器支持模板实例化的显式导出声明,但并非所有编译器都支持它(现在?)。

我建议看看这个gcc页面,它讨论了模板实例化的“cfront”和“borland”模型之间的权衡。

https://gcc.gnu.org/onlinedocs/gcc-4.6.4/gcc/Template-Instantiation.html

“borland”模型符合作者的建议,提供完整的模板定义,并多次编译。

它包含关于使用手动和自动模板实例化的明确建议。例如,“-repo”选项可用于收集需要实例化的模板。或者另一个选项是使用“-fno隐式模板”禁用自动模板实例化,以强制手动模板实例化。

根据我的经验,我依赖于为每个编译单元实例化的C++标准库和Boost模板(使用模板库)。对于我的大型模板类,我为所需的类型进行了一次手动模板实例化。

这是我的方法,因为我提供的是一个工作程序,而不是用于其他程序的模板库。这本书的作者Josuttis在模板库方面做了大量工作。

如果我真的担心速度,我想我会探索使用预编译头https://gcc.gnu.org/onlinedocs/gcc/Precompiled-Headers.html

这在许多编译器中得到了支持。然而,我认为使用模板头文件时,预编译头文件会很困难。

如果问题是将.h编译为使用它的所有.cpp模块的一部分所产生的额外编译时间和二进制大小膨胀,那么在许多情况下,您可以做的是使模板类从接口的非类型依赖部分的非模板化基类下降,并且该基类可以在.cpp文件中实现。