Детали создания шаблона компиляторов GCC и MS - PullRequest
44 голосов
/ 25 августа 2011

Может ли кто-нибудь предоставить сравнение или конкретную информацию о том, как происходит создание шаблона обрабатывается во время компиляции и / или компоновки в компиляторах GCC и MS? Этот процесс отличается в контексте статических библиотек, общих библиотек и исполняемых файлов? Я нашел этот документ о том, как GCC справляется с этим, но я не уверен, что информация все еще ссылается на текущее положение вещей. Должен ли я использовать флаги они предлагают там при компиляции моих библиотек, например -fno-неявные-шаблоны

Что я знаю (возможно, это не обязательно так):

  • шаблоны будут созданы при фактическом использовании
  • шаблоны будут созданы в результате явных реализаций
  • Дублирование экземпляров, как правило, обрабатывается путем свертывания дубликатов или откладывания создания экземпляров до времени ссылки

Ответы [ 2 ]

56 голосов
/ 30 августа 2011

Точка воплощения

шаблоны будут созданы при фактическом использовании

Не совсем, но примерно. Точная точка инстанцирования немного тонка, и я делегирую вас в раздел под названием Точка инстанцирования в прекрасной книге Вандевурда / Хосуттиса.

Однако компиляторы не обязательно правильно реализуют POI: Ошибка c ++ / 41995: Неправильная точка создания экземпляра для шаблона функции


Частичная реализация

шаблоны будут созданы при фактическом использовании

Это частично правильно. Это верно для шаблонов функций, но для шаблонов классов создаются только те функции-члены, которые используются. Ниже приведен правильный код:

#include <iostream>

template <typename> struct Foo {
    void let_me_stay() {
        this->is->valid->code. get->off->my->lawn;
    }

    void fun() { std::cout << "fun()" << std::endl; } 
};


int main () {
    Foo<void> foo;
    foo.fun();
}

let_me_stay() проверяется синтаксически (и синтаксис там правильный), но не семантически (то есть не интерпретируется).


Двухфазный поиск

Однако, только зависимый код интерпретируется позже; ясно, что в пределах Foo<>, this зависит от точного идентификатора шаблона, с которым создается экземпляр Foo<>, поэтому мы отложили проверку ошибок Foo<>::let_me_alone() до времени создания экземпляра.

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

$ cat non-dependent.cc
template <typename> struct Foo {
    void I_wont_compile() { Mine->is->valid->code. get->off->my->lawn; }
};
int main () {} // note: no single instantiation

Mine - совершенно неизвестный символ для компилятора, в отличие от this, для которого компилятор может определить его зависимость от экземпляра.

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


Полная реализация шаблонов классов

Определение Foo<>::let_me_stay() сработало, поскольку проверка ошибок была отложена на более поздний срок, как и для указателя this, который является зависимым. За исключением случаев, когда вы использовали бы

явные экземпляры

cat > foo.cc
#include <iostream>

template <typename> struct Foo {
    void let_me_stay() { this->is->valid->code. get->off->my->lawn; }
    void fun() { std::cout << "fun()" << std::endl; } 
};

template struct Foo<void>;
int main () {
    Foo<void> foo;
    foo.fun();
}

g++ foo.cc
error: error: ‘struct Foo<void>’ has no member named ‘is’


Шаблонные определения в разных единицах перевода

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

$ cat A.cc
template <typename> struct Foo {
    void fun();  // Note: no definition
};
int main () {
    Foo<void>().fun();
}

$ cat B.cc
#include <iostream>
template <typename> struct Foo {
    void fun();

};
template <typename T>
void Foo<T>::fun() { 
    std::cout << "fun!" << std::endl;
}  // Note: definition with extern linkage

template struct Foo<void>; // explicit instantiation upon void

$ g++ A.cc B.cc
$ ./a.out
fun!

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

$ cat A.cc
template <typename> struct Foo {
    void fun();  // Note: no definition
};
int main () {
    Foo<float>().fun();
}
$ g++ A.cc B.cc
undefined reference to `Foo<float>::fun()'

Небольшое замечание о двухфазном поиске. Реализует ли компилятор двухфазный поиск стандартом, не диктуется. Однако, чтобы соответствовать, он должен работать так, как если бы он работал (точно так же, как сложение или умножение не обязательно должны выполняться с использованием инструкций ЦП сложения или умножения.

0 голосов
/ 27 августа 2011

Редактировать : Оказывается, то, что я написал ниже, противоречит стандарту C ++. Это верно для Visual C ++, но неверно для компиляторов, которые используют «поиск по двухфазному имени».

Насколько я знаю, то, что вы говорите, правильно. Шаблоны будут создаваться при фактическом использовании (в том числе при объявлении в качестве члена другого типа, но не при упоминании в объявлении функции (у которого нет тела)) или в результате явных реализаций.

Проблема с шаблонами заключается в том, что если вы используете один и тот же шаблон (например, вектор) в нескольких различных единицах компиляции (файлы .cpp), компилятор повторяет работу создания экземпляра шаблона в каждом файле .cpp, тем самым замедляя компиляцию. IIRC, GCC имеет некоторый (нестандартный?) Механизм, который можно использовать, чтобы избежать этого (но я не использую GCC). Но Visual C ++ всегда повторяет эту работу, если только вы не используете явную реализацию шаблона в предварительно скомпилированном заголовке (но даже это замедлит компиляцию, поскольку загрузка файла PCH большего размера занимает больше времени.) После этого компоновщик удаляет дубликаты. Примечание : комментарий ниже, связанный с страницей , который говорит нам, что не все компиляторы работают таким образом. Некоторые компиляторы откладывают создание экземпляров функций до времени компоновки, что должно быть более эффективным.

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

void Test() { fdsh "s.w" = 6; wtf? }

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

Я ожидаю, что статические библиотеки (и объектные файлы) будут хранить объектный код всех шаблонов, которые были созданы. Но если ваша программа имеет определенную статическую библиотеку в качестве зависимости, вы не можете вызвать функции шаблона, которые уже были созданы в ней, по крайней мере, не в VC ++, для чего всегда требуется исходный код (с телами функций) класса шаблона в Для вызова функций в нем.

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

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