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


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, за исключением, конечно, одновременного использования других нестандартных расширений компилятора.


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>;

0

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

Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.