за допомогою шаблону 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
Це зовсім не правильне використання зовнішніх шаблонів ... це навіть не компілюється
Dani

Ви можете трохи часу сформулювати (одне) питання? Для чого ви публікуєте код? Я не бачу питання, пов'язаного з цим. Також extern template class foo<int>();здається помилкою.
вересень

@Dani> він збирається чудово на моїй візуальній студії 2010, за винятком попереджувального повідомлення: Попередження 1 попередження C4231: нестандартне розширення, що використовується: 'extern' перед явним встановленням шаблону
codekiddy

2
@sehe питання дуже просте: як і коли використовувати ключове слово для зовнішнього шаблону? (Шаблон Extern - це C ++ 0x нове майбутнє btw), ви сказали: "Крім того, клас шаблону" extern "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: найкраще пояснення зовнішніх шаблонів, які я читав досі!
П'єтро

90
"якщо ви знаєте, що його використовується в тому ж бінарному файлі десь ще." Цього не достатньо і не потрібно. Ваш код "неправильно сформований, діагностика не потрібна". Вам не дозволено покладатися на неявну інстанцію іншої TU (компілятору дозволено оптимізувати її за межами, подібно до вбудованої функції). Ясна інстанція повинна бути надана в іншому ТУ.
Йоханнес Шауб - ліб

32
Я хотів би зазначити, що ця відповідь, ймовірно, неправильна, і я її покусав. На щастя, коментар Йоганнеса мав декілька голосів, і я цього разу приділив більше уваги цьому. Я можу лише припустити, що переважна більшість виборців з цього питання насправді не реалізували ці типи шаблонів у декількох одиницях компіляції (як я це робила сьогодні) ... Принаймні, для кланг, єдиним вірним способом є внесення цих визначень шаблонів у ваш заголовок! Будьте попереджені!
Стівен Лу

6
@ JohannesSchaub-litb, ви могли б трохи детальніше розглянути або, можливо, дати кращу відповідь? Я не впевнений, чи повністю зрозумів ваші заперечення.
andreee

48

У Вікіпедії є найкращий опис

У C ++ 03 компілятор повинен інстанціювати шаблон, коли в блоці перекладу зустрічається повністю визначений шаблон. Якщо шаблон інстанціюється тими ж типами у багатьох одиницях перекладу, це може значно збільшити час компіляції. Немає способу запобігти цьому в C ++ 03, тому C ++ 11 запровадив декларації шаблону зовнішнього типу, аналогічні деклараціям зовнішніх даних.

У C ++ 03 є цей синтаксис, щоб зобов'язати компілятор створити екземпляр шаблону:

  template class std::vector<MyClass>;

Тепер C ++ 11 надає цей синтаксис:

  extern template class std::vector<MyClass>;

що повідомляє компілятору не інстанціювати шаблон у цьому блоці перекладу.

Попередження: nonstandard extension used...

Microsoft VC ++ використовував нестандартну версію цієї функції вже кілька років (на C ++ 03). Компілятор попереджає про це, щоб запобігти проблемам переносимості з кодом, який також потрібно було компілювати на різних компіляторах.

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


tnx для вашої відповіді, так, що ця фактичність означає, що майбутнє "зовнішній шаблон" повністю працює для VS 2010, і ми можемо просто ігнорувати попередження? (використовуючи, наприклад, прагму, щоб ігнорувати повідомлення), і будьте потрібні, щоб шаблон не інстанціювався більше, ніж у часі в VSC ++. компілятор. Дякую.
codekiddy

4
"... що вказує компілятору не інстанціювати шаблон у цьому блоці перекладу ." Я не думаю, що це правда. Будь-який метод, визначений у визначенні класу, вважається вбудованим, тому якщо реалізація STL використовує вбудовані методи std::vector(майже впевнені, що всі вони це роблять), externце не має ефекту.
Андреас Гафербург

Так, ця відповідь вводить в оману. MSFT doc: "Ключове слово" екстерн "у спеціалізації застосовується лише до функцій-членів, визначених поза тілом класу. Функції, визначені всередині декларації класу, вважаються вбудованими функціями і завжди створюються інстанцією." На жаль, всі класи STL у VS (востаннє перевірений був у 2017 році) мають на жаль лише вбудовані методи.
0kcats

Це стосується всіх заяв інлайн незалежно від того, де вони виникають, завжди @ 0kcats
sehe

@sehe Посилання на Вікі з прикладом std :: vector та посиланням на MSVC у тому самому відповіді змушує вважати, що в використанні extern std :: vector в MSVC може бути якась користь, поки її поки немає. Не впевнений, чи це вимога стандарту, можливо, інші компілятори мають таку ж проблему.
0kcats

7

extern template потрібен лише в тому випадку, якщо декларація шаблону завершена

На це натякали й інші відповіді, але я не думаю, що цьому було приділено достатньо уваги.

Це означає, що в прикладах ОП це 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означає невизначене, тому визначення залишається лише за 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

Якщо ви раніше використовували зовнішні функції для функцій, то для шаблонів дотримується точно таку філософію. якщо ні, то допомога зовнішнього вигляду для простих функцій може допомогти. Також ви можете поставити екстерн (и) у файл заголовка та включити заголовок, коли вам це потрібно.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.