С использованием шаблона extern (С++ 11)

Рисунок 1: шаблоны функций

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){
   //...
}    
//explicit instantation
template void f<T>();

main.cpp

#include "TemplHeader.h"
extern template void f<T>(); //is this correct?
int main() {
    f<char>();
    return 0;
}

Правильно ли это использовать extern template, или я использую это ключевое слово только для шаблонов классов, как показано на рисунке 2?

Рисунок 2: Шаблоны классов

TemplHeader.h

template<typename T>
class foo {
    T f();
};

TemplCpp.cpp

template<typename T>
void foo<T>::f() {
    //...
}
//explicit instantation
template class foo<int>;

main.cpp

#include "TemplHeader.h"
extern template class foo<int>();
int main() {
    foo<int> test;
    return 0;
}

Я знаю, что хорошо разместить все это в одном файле заголовка, но если мы создадим шаблоны с одинаковыми параметрами в нескольких файлах, мы получим несколько одинаковых определений, и компилятор удалит их все (кроме одного), чтобы избежать ошибки. Как использовать extern template? Можем ли мы использовать его только для классов, или мы можем использовать его для функций тоже?

Кроме того, рис. 1 и рис. 2 могут быть расширены до решения, в котором шаблоны находятся в одном файле заголовка. В этом случае нам нужно использовать ключевое слово extern template, чтобы избежать нескольких одинаковых настроек. Это только для классов или функций?

Ответ 1

Вы должны использовать extern template, чтобы заставить компилятор не создавать экземпляр шаблона, когда вы знаете, что он будет создан где-то в другом месте. Он используется для уменьшения времени компиляции и размера объектного файла.

Например:

// header.h

template<typename T>
void ReallyBigFunction()
{
    // Body
}

// source1.cpp

#include "header.h"
void something1()
{
    ReallyBigFunction<int>();
}

// source2.cpp

#include "header.h"
void something2()
{
    ReallyBigFunction<int>();
}

Это приведет к следующим объектным файлам:

source1.o
    void something1()
    void ReallyBigFunction<int>()    // Compiled first time

source2.o
    void something2()
    void ReallyBigFunction<int>()    // Compiled second time

Если оба файла связаны друг с другом, один void ReallyBigFunction<int>() будет отброшен, что приведет к потерянному времени компиляции и размерному размеру объекта.

Чтобы не тратить время компиляции и размер объектного файла, существует ключевое слово extern, которое заставляет компилятор не компилировать функцию шаблона. Вы должны использовать это, если и только если вы знаете, что он используется в том же двоичном файле где-то еще.

Изменение source2.cpp на:

// source2.cpp

#include "header.h"
extern template void ReallyBigFunction<int>();
void something2()
{
    ReallyBigFunction<int>();
}

В результате появятся следующие объектные файлы:

source1.o
    void something1()
    void ReallyBigFunction<int>() // compiled just one time

source2.o
    void something2()
    // No ReallyBigFunction<int> here because of the extern

Когда оба из них будут связаны друг с другом, второй объектный файл будет просто использовать символ из первого объектного файла. Нет необходимости отбрасывать и не тратить время на сбор и размер объектного файла.

Это должно использоваться только в проекте, например, в том случае, когда вы используете шаблон типа vector<int> несколько раз, вы должны использовать extern во всех, кроме одного исходного файла.

Это также относится к классам и функционирует как одна и даже функции членов шаблона.

Ответ 2

В Википедии есть лучшее описание

В С++ 03 компилятор должен создавать экземпляр шаблона всякий раз, когда полностью заданный шаблон встречается в блоке трансляции. Если шаблон создается с одинаковыми типами в многие единицы перевода, это может значительно увеличить время компиляции. Невозможно предотвратить это в С++ 03, поэтому С++ 11 представил объявления extern шаблонов, аналогичные extern объявления данных.

С++ 03 имеет этот синтаксис, чтобы заставить компилятор создать экземпляр шаблона:

  template class std::vector<MyClass>;

С++ 11 теперь предоставляет этот синтаксис:

  extern template class std::vector<MyClass>;

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

Предупреждение: nonstandard extension used...

Microsoft VС++ уже несколько лет использует нестандартную версию этой функции (на С++ 03). Компилятор предупреждает об этом, чтобы предотвратить проблемы с переносимостью с кодом, который необходим для компиляции на разных компиляторах.

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

Ответ 3

Известной проблемой с шаблонами является раздувание кода, которое является следствием генерации определения класса в каждом модуле, который вызывает специализацию шаблона класса. Чтобы предотвратить это, начиная с С++ 0x, можно использовать ключевое слово extern перед специализацией шаблона класса.

#include <MyClass> extern template class CMyClass<int>;

Явное создание экземпляра класса шаблона должно происходить только в одной единице перевода, предпочтительно с определением шаблона (MyClass.cpp).

template class CMyClass<int>;
template class CMyClass<float>;

Ответ 4

Если вы использовали extern для функций раньше, для шаблонов применяется точно такая же философия. если нет, то, если extern для простых функций может помочь. Кроме того, вы можете захотеть поместить extern (s) в заголовочный файл и включить заголовок, когда вам это нужно.