Подтвердить что ты не робот

Является ли шаблон extern препятствовать встраиванию функций?

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

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

//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);
}

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

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

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

4b9b3361

Ответ 1

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

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

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

EDIT: для любопытных я просто сделал быстрый тест, чтобы узнать, сколько времени g++ тратит на создание шаблонов. Я попытался создать экземпляр std::sort<int*> в разных количествах единиц перевода, причем и без создания экземпляра. Результат был окончательным: 30 мс на экземпляр std:: sort. Там определенно время для сохранения здесь в большом проекте.

Ответ 2

Использование 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 во время ссылки. Для полноты, вот makefile

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 и римейк).

Ответ 3

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

#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 это вызывает неинтерминированный вызов строки:: find BUT, а также строчный вызов string:: size.

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

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