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

116

Рисунок 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ключевое слово, чтобы избежать нескольких одинаковых мгновений. Это тоже только для классов или функций?

codekiddy
источник
3
Это совсем не правильное использование шаблонов extern ... это даже не компилируется
Дэни
Не могли бы вы выделить время, чтобы более четко сформулировать (один) вопрос? Для чего вы публикуете код? Я не вижу вопросов по этому поводу. Тоже extern template class foo<int>();похоже на ошибку.
sehe
@Dani> он отлично компилируется в моей Visual Studio 2010, за исключением предупреждающего сообщения: Предупреждение 1 предупреждение C4231: использовано нестандартное расширение: 'extern' перед явным экземпляром шаблона
codekiddy
2
@sehe вопрос очень простой: как и когда использовать ключевое слово extern template? (extern template - это C ++ 0x new future btw) вы сказали: «Кроме того, extern template class foo <int> (); кажется ошибкой». нет, у меня есть новая книга по C ++ и этот пример из моей книги.
codekiddy
1
@codekiddy: тогда визуальная студия действительно тупая ... во втором прототип не соответствует реализации, и даже если я исправлю это, рядом ()с внешней строкой написано «ожидаемый неквалифицированный идентификатор» . и ваша книга, и визуальная студия ошибаются, попробуйте использовать более стандартный компилятор, такой как g ++ или clang, и вы увидите проблему.
Дэни

Ответы:

181

Вы должны использовать только 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
@codekiddy: я понятия не имею, что под этим подразумевает Visual Studio. Вам действительно следует использовать более совместимый компилятор, если вы хотите, чтобы большая часть кода C ++ 11 работала правильно.
Дэни
4
@Dani: лучшее объяснение шаблонов extern, которое я читал!
Пьетро
90
"если вы знаете, что он используется в том же двоичном файле где-то еще.". Этого недостаточно и не требуется. Ваш код «неправильно сформирован, диагностика не требуется». Вам не разрешается полагаться на неявное создание экземпляра другого TU (компилятору разрешено оптимизировать его, как и встроенную функцию). Явное создание экземпляра должно быть предоставлено в другом TU.
Йоханнес Шауб - лит
32
Хочу отметить, что этот ответ, вероятно, неправильный, и меня он укусил. К счастью, комментарий Йоханнеса получил несколько голосов «за», и на этот раз я уделил ему больше внимания. Я могу только предположить, что подавляющее большинство голосующих по этому вопросу на самом деле не реализовали эти типы шаблонов в нескольких единицах компиляции (как я сделал сегодня) ... По крайней мере, для clang единственный верный способ - поместить эти определения шаблонов в ваш заголовок! Имейте в виду!
Стивен Лу
6
@ JohannesSchaub-litb, не могли бы вы рассказать немного подробнее или дать лучший ответ? Я не уверен, что полностью понял ваши возражения.
andreee
48

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

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

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

  template class std::vector<MyClass>;

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

  extern template class std::vector<MyClass>;

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

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

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

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

sehe
источник
tnx для вашего ответа sehe, так что это означает, что "extern template" future полностью работает для VS 2010, и мы можем просто игнорировать предупреждение? (например, используя прагму для игнорирования сообщения) и будьте уверены, что шаблон не создается более чем вовремя в VSC ++. компилятор. Спасибо.
codekiddy
4
«... который сообщает компилятору не создавать экземпляр шаблона в этой единице перевода ». Я не думаю, что это правда. Любой метод, определенный в определении класса, считается встроенным, поэтому, если реализация STL использует встроенные методы для std::vector(почти наверняка все они), не externимеет никакого эффекта.
Андреас Хафербург
Да, этот ответ вводит в заблуждение. Документ MSFT: «Ключевое слово extern в специализации применяется только к функциям-членам, определенным вне тела класса. Функции, определенные внутри объявления класса, считаются встроенными функциями и всегда создаются». К сожалению, все классы STL в VS (последняя проверка была в 2017 году) имеют только встроенные методы.
0kcats
Это касается всех встроенных объявлений, независимо от того, где они возникают, всегда @ 0kcats
см.
@sehe Ссылка на Wiki с примером std :: vector и ссылка на MSVC в том же ответе наводит на мысль, что может быть некоторая выгода от использования extern std :: vector в MSVC, хотя пока нет. Не уверен, что это требование стандарта, возможно, у других компиляторов такая же проблема.
0kcats
7

extern template требуется только в том случае, если объявление шаблона завершено

На это намекали в других ответах, но я не думаю, что этому уделялось достаточно внимания.

Это означает, что в примерах OPs это не extern templateимеет никакого эффекта, потому что определения шаблонов в заголовках были неполными:

  • void f();: просто декларация, без тела
  • class foo: объявляет метод, f()но не имеет определения

Поэтому я бы рекомендовал просто удалить extern templateопределение в этом конкретном случае: вам нужно только добавить их, если классы полностью определены.

Например:

TemplHeader.h

template<typename T>
void f();

TemplCpp.cpp

template<typename T>
void f(){}

// Explicit instantiation for char.
template void f<char>();

main.cpp

#include "TemplHeader.h"

// Commented out from OP code, has no effect.
// extern template void f<T>(); //is this correct?

int main() {
    f<char>();
    return 0;
}

компилировать и просматривать символы с помощью nm:

g++ -std=c++11 -Wall -Wextra -pedantic -c -o TemplCpp.o TemplCpp.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -c -o Main.o Main.cpp
g++ -std=c++11 -Wall -Wextra -pedantic -o Main.out Main.o TemplCpp.o
echo TemplCpp.o
nm -C TemplCpp.o | grep f
echo Main.o
nm -C Main.o | grep f

вывод:

TemplCpp.o
0000000000000000 W void f<char>()
Main.o
                 U void f<char>()

а затем, как man nmмы видим, это Uозначает undefined, поэтому определение осталось только по TemplCppжеланию.

Все это сводится к компромиссу между полными объявлениями заголовков:

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

Дальнейшие их примеры показаны по адресу: Явное создание шаблона - когда оно используется?

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

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

Протестировано в Ubuntu 18.04.

Чиро Сантилли 郝海东 冠状 病 六四 事件 法轮功
источник
4

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

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

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

template class CMyClass<int>;
template class CMyClass<float>;
damirlj
источник
0

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

QQQQQ
источник