Почему шаблоны могут быть реализованы только в заголовочном файле? - PullRequest
1579 голосов
/ 30 января 2009

Цитата из Стандартная библиотека C ++: учебное пособие и справочник :

В настоящее время единственным переносимым способом использования шаблонов является их внедрение в заголовочные файлы с использованием встроенных функций.

Почему это?

(Пояснение: заголовочные файлы не являются переносимым решением only . Но они являются наиболее удобным переносимым решением.)

Ответы [ 16 ]

6 голосов
/ 17 сентября 2011

Это совершенно правильно, потому что компилятор должен знать, какой он тип для выделения. Таким образом, классы шаблонов, функции, перечисления и т. Д. Также должны быть реализованы в заголовочном файле, если он должен быть общедоступным или частью библиотеки (статической или динамической), поскольку заголовочные файлы НЕ компилируются в отличие от файлов c / cpp являются. Если компилятор не знает, тип не может скомпилировать его. В .Net это возможно, потому что все объекты являются производными от класса Object. Это не .Net.

2 голосов
/ 19 июля 2018

Просто чтобы добавить что-то примечательное здесь. Можно просто определить методы шаблонного класса в файле реализации, когда они не являются шаблонами функций.


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;

    ...
}
2 голосов
/ 13 мая 2017

Способ иметь отдельную реализацию заключается в следующем.

//inner_foo.h

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 имеет предварительные объявления. foo.tpp имеет реализацию и включает inner_foo.h; и foo.h будет содержать только одну строку, чтобы включить foo.tpp.

Во время компиляции содержимое файла foo.h копируется в foo.tpp, а затем весь файл копируется в файл foo.h, после чего он компилируется. Таким образом, нет никаких ограничений, и наименование является последовательным, в обмен на один дополнительный файл.

Я делаю это, потому что статические анализаторы кода ломаются, когда он не видит предварительные объявления класса в * .tpp. Это раздражает, когда вы пишете код в любой IDE или используете YouCompleteMe или другие.

1 голос
/ 19 июля 2016

Компилятор будет генерировать код для каждого экземпляра шаблона, когда вы используете шаблон на этапе компиляции. В процессе компиляции и компоновки файлы .cpp преобразуются в чистый объектный или машинный код, который содержит ссылки или неопределенные символы, поскольку файлы .h, включенные в ваш main.cpp, не имеют реализации YET. Они готовы быть связаны с другим объектным файлом, который определяет реализацию для вашего шаблона, и, таким образом, у вас есть полный исполняемый файл a.out. Однако, поскольку шаблоны необходимо обрабатывать на этапе компиляции, чтобы генерировать код для каждого экземпляра шаблона, который вы делаете в своей основной программе, связывание не поможет, потому что компиляция main.cpp в main.o, а затем компиляция вашего шаблона .cpp в template.o, и тогда связывание не достигнет цели шаблона, потому что я связываю инстанцирование другого шаблона с одной и той же реализацией шаблона! И шаблоны должны делать противоположное, т. Е. Иметь ОДНУ реализацию, но допускают множество доступных реализаций с использованием одного класса.

Значение typename T get заменено на этапе компиляции, а не на этапе компоновки, поэтому, если я попытаюсь скомпилировать шаблон без замены T в качестве конкретного типа значения, поэтому он не будет работать, поскольку это определение шаблонов. процесс компиляции, и, кстати, метапрограммирование - все об использовании этого определения.

0 голосов
/ 19 марта 2019

Еще одна причина, по которой хорошей идеей является запись деклараций и определений в заголовочных файлах, - это удобочитаемость. Предположим, в 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 здесь реализовывал оператор меньше чем (<). Он сгенерирует ошибку компилятора, когда вы сравните два экземпляра класса, которые не реализовали «<». </p>

Поэтому, если вы разделите объявление и определение шаблона, вы не сможете прочитать только заголовочный файл, чтобы увидеть все входы и выходы этого шаблона, чтобы использовать этот API в своих собственных классах, хотя компилятор скажет Вы в этом случае о том, какой оператор должен быть переопределен.

0 голосов
/ 11 марта 2019

Вы можете определить свой класс шаблона внутри файла .template, а не файла .cpp. Тот, кто говорит, что вы можете определить его только внутри заголовочного файла, ошибается. Это то, что работает вплоть до c ++ 98.

Не забудьте, что ваш компилятор рассматривает ваш файл .template как файл c ++, чтобы сохранить смысл intelli.

Вот пример этого для класса динамического массива.

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