Предотвращает ли шаблон extern вставку функций? - PullRequest
27 голосов
/ 17 июля 2011

Мне не совсем понятно, как новая функция extern template предназначена для работы в C ++ 11.Я понимаю, что он предназначен для ускорения времени компиляции и упрощения связывания проблем с общими библиотеками.Означает ли это, что компилятор даже не анализирует тело функции, заставляя сделать необъявленный вызов?Или он просто указывает компилятору не генерировать фактическое тело метода при выполнении не встроенного вызова?Очевидно, что генерация кода времени ссылки не выдерживает.

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

//Common header
template<typename T>
void DeleteMe(T* t) {
    delete t;
}

struct Incomplete;
extern template void DeleteMe(Incomplete*);

//Implementation file 1
#include common_header
struct Incomplete { };
template void DeleteMe(Incomplete*);

//Implementation file 2
#include common_header
int main() {
   Incomplete* p = factory_function_not_shown();
   DeleteMe(p);
}

Within "Файл реализации2 ", небезопасно delete указатель на Incomplete.Так что встроенная версия DeleteMe потерпит неудачу.Но если его оставить как фактический вызов функции, а сама функция была сгенерирована в «файле реализации 1», все будет работать правильно.

Как следствие, правила одинаковы для функций-членов шаблонных классовс аналогичной декларацией extern template class?

В экспериментальных целях MSVC выдает правильный вывод для вышеприведенного кода, но при удалении строки extern выдает предупреждение об удалении неполного типа.Тем не менее, это остатки нестандартного расширения, которое они представили несколько лет назад, поэтому я не уверен, насколько я могу доверять этому поведению.У меня нет доступа к какой-либо другой среде сборки, чтобы экспериментировать [сохранить ideone et al., Но ограниченность одной единицей перевода в этом случае довольно ограничивает].

Ответы [ 3 ]

8 голосов
/ 18 июля 2011

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

Как вы знаете, в C ++ 03 вы можете явно создать экземпляр шаблона, используя следующий синтаксис:

template class SomeTemplateClass<int>;
template void foo<bool>();

Это говорит компилятору создать экземпляр шаблона в текущем модуле перевода. Однако это не останавливает неявные реализации: компилятор все еще должен выполнить все неявные реализации, а затем снова объединить их во время компоновки.

Пример:

// a.h
template <typename> void foo() { /* ... */ }

// a.cpp
#include "a.h"
template void foo<int>();

// b.cpp
#include "a.h"
int main()
{
    foo<int>();
    return 0;
} 

Здесь a.cpp явно создает экземпляр foo<int>(), но как только мы пойдем на компиляцию b.cpp, он снова создаст его экземпляр, потому что b.cpp даже не подозревает, что a.cpp все равно будет его создавать. Для больших функций с множеством различных модулей перевода, выполняющих неявные реализации, это может значительно увеличить время компиляции и компоновки. Это также может привести к излишнему встраиванию функции, что может привести к значительному расширению кода.

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

// a.h
template <typename> void foo() { /* ... */ }
extern template void foo<int>();

Таким образом, b.cpp не вызовет создание экземпляра foo<int>(). Функция будет создана в a.cpp и будет связана как любая нормальная функция. Это также намного менее вероятно, будет встроено.

Обратите внимание, что это не предотвращает встраивание - функция все еще может быть встроена во время соединения точно так же, как обычная не встроенная функция все еще может быть встроена.

РЕДАКТИРОВАТЬ: Для тех, кто любопытно, я просто сделал быстрый тест, чтобы увидеть, сколько времени g ++ тратит на создание шаблонов. Я попытался создать экземпляр std::sort<int*> в различном количестве единиц перевода, при этом исключение было прекращено. Результат был убедительным: 30 мс на каждый экземпляр std :: sort . Здесь определенно есть время сэкономить в большом проекте.

3 голосов
/ 09 ноября 2015

Использование extern template class, похоже, не предотвращает встраивание. Я проиллюстрирую это на примере, он немного сложен, но самое простое, что я могу придумать.

В файле a.h мы определяем шаблон класса CFoo,

#ifndef A_H
#define A_H
#include <iostream>

template <typename T> class CFoo{
  public: CFoo(){
      std::cout << "CFoo Constructor, edit 0" << std::endl;
    }
};

extern template class CFoo<int>;
#endif

В конце a.h мы используем extern template class CFoo<int>, чтобы указать любой единице перевода с #include a.h, что ей не нужно генерировать какой-либо код для CFoo. Мы обещаем, что все вещи CFoo будут гладко связаны.

В файле c.cpp у нас есть

#include "a.h"

void run(){
  CFoo<int> cf;
}

В связи с необходимостью extern template class promise' at the end of a.h, the translation unit of c.cpp does not генерировать любой код для класса CFoo.

Наконец, мы объявляем главную функцию в b.cpp,

void run();
int main(){
  run();
  return 0;
}

В b.cpp нет ничего фантастического, мы просто объявляем void run(), что будет связано с реализацией модуля перевода b.cpp во время компоновки. Для полноты вот make-файл

cflags = -std=c++11 -O1

b : b.o a.o c.o
  g++ ${cflags} b.o a.o c.o -o b

b.o : b.cpp 
  g++ ${cflags} -c b.cpp -o b.o

c.o : c.cpp 
  g++ ${cflags} -c c.cpp -o c.o

a.o : a.cpp a.h
  g++ ${cflags} -c a.cpp -o a.o

clean:
  rm -rf a.o b.o c.o b

Используя этот make-файл, компилируется и связывается исполняемый файл a , который выводит `` CFoo Constructor, edit 0 '' при запуске. Но обратите внимание! В приведенном выше примере мы, кажется, нигде не объявили CFoo<int>: CFoo<int> определенно не объявлено в модуле перевода b.cpp, так как заголовок не отображается в этом модуле перевода, и модулю перевода c.cpp сказали, что он не нужно было реализовывать CFoo. Так что же происходит?

Внесите одно изменение в make-файл: замените -O1 на -O0 и сделайте чистый make

Теперь вызов по ссылке приводит к ошибке (при использовании gcc 4.8.4)

c.o: In function `run()':
c.cpp:(.text+0x10): undefined reference to `CFoo<int>::CFoo()'

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

Чтобы получить связь с -O1, нам нужно выполнить наше обещание и предоставить реализацию CFoo, которую мы предоставляем в файле a.cpp

#include "a.h"
template void foo<int>();

Теперь мы можем быть уверены, что CFoo появится в модуле перевода a.cpp, и наше обещание будет выполнено. Кроме того, обратите внимание, что template void foo<int>() в a.cpp предшествует extern template void foo<int>() через включение a.h, что не является проблематичным.

Наконец, я нахожу это непредсказуемое поведение, зависящее от оптимизации, раздражающим, поскольку это означает, что изменения в ah и перекомпиляция a.cpp могут не отражаться в run(), как ожидалось, если бы не было встраивания (попробуйте изменить стандартный вывод конструктора Foo и переделать).

0 голосов
/ 09 сентября 2017

Вот интересный пример:

#include <algorithm>
#include <string>

extern template class std::basic_string<char>;
int foo(std::string s)
{
    int res = s.length();
    res += s.find("some substring");
    return res;
}

При компиляции с g ++ - 7.2 при -O3 это вызывает не встроенный вызов string :: find НО встроенный вызов string :: size.

Хотя без внешнего шаблона все действительно встроено.Clang ведет себя так же, и MSVC практически не может ничего встроить в любом случае.

Таким образом, ответ: это зависит, и компиляторы могут иметь специальные эвристики для этого.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...