Как компилятор знает, как использовать специализацию шаблона вместо его собственной реализации? - PullRequest
3 голосов
/ 13 января 2010

Рассмотрим следующие файлы:

Foo.H

template <typename T>
struct Foo
{
  int foo();
};

template <typename T>
int Foo<T>::foo()
{
  return 6;
}

Foo.C

#include "Foo.H"

template <>
int Foo<int>::foo()
{
  return 7;
}

main.C

#include <iostream>
#include "Foo.H"

using namespace std;

int main()
{
  Foo<int> f;
  cout << f.foo() << endl;
  return 0;
}

Когда я компилирую и запускаю, печатается 7.Что тут происходит?Когда создаются шаблоны?Если компилятор делает это, как он узнает, что ему не нужно создавать свою собственную версию Foo?

Ответы [ 4 ]

17 голосов
/ 13 января 2010

Проблема в том, что вы нарушили одно правило определения. В main.C вы включили Foo.H, но не Foo.C (что имеет смысл, поскольку это исходный файл). Когда main.C компилируется, компилятор не знает, что вы специализировали шаблон в Foo.C, поэтому он использует универсальную версию (которая возвращает 6) и компилирует класс Foo. Затем, когда он компилирует Foo.C, он видит полную специализацию, которую он может скомпилировать сразу - ему не нужно ждать, пока он где-нибудь будет создан, потому что все типы заполнены (если вы у него было два параметра шаблона и только специализированный (это было бы не так), и он компилирует новый и отличный класс Foo.

Обычно множественные определения для одной и той же вещи вызывают ошибку компоновщика. Но экземпляры шаблона являются «слабыми символами», что означает, что допускается несколько определений. Линкер предполагает, что все определения действительно одинаковы , а затем выбирает одно случайное (ну, вероятно, последовательно первое или последнее, но только как совпадение реализации).

Зачем делать их слабыми символами? Поскольку Foo может использоваться в нескольких исходных файлах, каждый из которых компилируется индивидуально, и каждый раз, когда Foo используется в модуле компиляции, генерируется новый экземпляр. Обычно они избыточны, поэтому имеет смысл выбросить их. Но вы нарушили это предположение, предоставив специализацию в одном модуле компиляции (foo.C), но не в другом (main.C).

Если вы объявляете специализацию шаблона в Foo.H, то когда main.C скомпилирован, он не генерирует экземпляр Foo, таким образом гарантируя, что в вашей программе существует только одно определение.

2 голосов
/ 13 января 2010

Я предполагаю, что компилятор создает экземпляр Foo, но затем при связывании выбирает специализированный Foo.

1 голос
/ 13 января 2010

При компиляции main.c компилятор не знает о вашей специализации. Я предполагаю, что он должен генерировать свою собственную версию Foo<int>::foo() на основе неспециализированного шаблона.

Но тогда, при связывании, компоновщик видит, что существует специализация для Foo<int>::foo(). Следовательно, он помещает в исполняемый файл специализированную версию.

В конце концов, даже если он не знает об этом во время компиляции, main.c вызовет специализированную версию Foo<int>::foo().

0 голосов
/ 13 января 2010

Шаблоны генерируют разные классы для каждой комбинации параметров шаблона. Это происходит во время компиляции, и поэтому шаблоны должны находиться в заголовках. Вы специализация make для параметра int, и компилятор вызывает Foo<int>::foo() для вашей переменной f. Это похоже на переопределение виртуальной функции, но во время компиляции.

...