Сведения о создании шаблона компиляторов GCC и MS

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

То, что я знаю (возможно, не обязательно правильно), заключается в следующем:

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

Ответ 1


Точка инстанцирования

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

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

Однако компиляторы не обязательно правильно реализуют POI: Ошибка С++/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++ предполагают, что шаблоны не обрабатываются вообще до создания экземпляра, но это только миф, приходящий из,..., Microsoft С++).


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

Определение 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()'

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

Ответ 2

Изменить. Оказывается, то, что я написал ниже, противоречит стандарту С++. Это верно для Visual С++, но false для компиляторов, которые используют "двухфазный поиск имени".

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

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

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

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

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

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

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