Шаблонный класс, специализированный внутри и снаружи lib - PullRequest
3 голосов
/ 29 июля 2011

Рассмотрим этот синтетический пример.У меня есть два собственных проекта C ++ в моем решении Visual Studio 2010.Один - это консоль exe, а другой - lib.

В lib есть два файла:

// TImage.h

template<class T> class TImage
{
public:
  TImage()
  {
#ifndef _LIB
    std::cout << "Created (main), ";
#else
    std::cout << "Created (lib), ";
#endif
    std::cout << sizeof(TImage<T>) << std::endl;
  }

#ifdef _LIB
  T c[10];
#endif
};

void CreateImageChar();
void CreateImageInt();

и

// TImage.cpp

void CreateImageChar()
{
  TImage<char> image;
  std::cout << sizeof(TImage<char>) << std::endl;
}
void CreateImageInt()
{
  TImage<int> image;
  std::cout << sizeof(TImage<int>) << std::endl;
}

И один файл в exe:

// main.cpp

int _tmain(int argc, _TCHAR* argv[])
{
  TImage<char> image;
  std::cout << sizeof(TImage<char>) << std::endl;

  CreateImageChar();
  CreateImageInt();

  return 0;
}

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

// cout:
Created (main), 1
1
Created (main), 1
10
Created (lib), 40
40

Так как именно это произошло, этот компоновщик переопределяет версию lib TImage<char> в версии exe TImage<char>?Но поскольку exe-версия TImage<int> не существует, она сохраняет версию lib TImage<int>? .. Стандартизировано ли это поведение, и если да, то где я могу найти описание?

Update : объяснения эффекта, приведенные ниже, правильные, спасибо.Но вопрос был в том, «как именно это произошло»? .. Я ожидал получить ошибку компоновщика , например, «множественно определенные символы».Таким образом, наиболее подходящий ответ от Ответ Антонио Переса .

Ответы [ 6 ]

2 голосов
/ 29 июля 2011

Код шаблона создает дублированный код объекта.

Компилятор копирует код шаблона для типа, который вы указываете при создании экземпляра шаблона.Таким образом, когда TImage.cpp компилируется, вы получаете объектный код для двух версий вашего шаблона, один для char и один для int в TImage.o.Затем main.cpp компилируется, и вы получаете новую версию вашего шаблона для char в main.o.Тогда компоновщик использует тот, который в main.o всегда .

Это объясняет, почему ваш вывод выдает «Созданные» строки.Но было немного странно видеть несоответствие в строках 3, 4 относительно размера объекта:

Created (main), 1
10

Это связано с тем, что компилятор разрешил оператор sizeof во время компиляции.

1 голос
/ 29 июля 2011

Я предполагаю, что вы создаете статическую библиотеку, потому что у вас нет __decelspec(dllexport) или extern "C" в коде.Здесь происходит следующее.Компилятор создает экземпляр TImage<char> и TImage<int> для вашей библиотеки.Он также создает экземпляр для вашего исполняемого файла.Когда компоновщик присоединяется к статической библиотеке и объектам исполняемого файла вместе, дублирующийся код удаляется.Обратите внимание, что статические библиотеки связаны в подобном объектном коде, поэтому нет никакой разницы, если вы создаете один большой исполняемый файл или несколько статических библиотек и исполняемый файл.Если вы создадите один исполняемый файл, результат будет зависеть от порядка, в котором связаны объекты;он же «не определен».

Если вы измените библиотеку на DLL, поведение изменится.Так как вы звоните через границу DLL, каждому нужна их копия TImage<char>.В большинстве случаев библиотеки DLL ведут себя лучше, чем вы ожидаете, что библиотека будет работать.Статические библиотеки обычно просто удобны, поэтому вам не нужно помещать код в ваш проект.

Примечание. Это относится только к Windows.В системах POSIX * .a файлы ведут себя как файл * .so, что создает немало головной боли для разработчиков компиляторов.

Редактирование: Просто никогда не передавайте класс TImage через границу DLL.Это обеспечит крах.Это та же самая причина, по которой реализация std :: string от Microsoft дает сбой при смешивании сборок отладки и выпуска.Они делают именно то, что вы делали только с макросом NDEBUG.

0 голосов
/ 29 июля 2011

В вашей библиотеке у вас есть TImage, который печатает "lib" при построении и содержит массив T.Есть два из них, один для int и один для char.

. В основном у вас есть TImage, который печатает "main" в конструкции и не содержит массив T.В этом случае есть только версия char;потому что вы никогда не просите создать версию int.

Когда вы переходите по ссылке, компоновщик выбирает один из двух конструкторов TImage<char> в качестве официального;случается, выбрал версию main.Вот почему ваша третья строка печатает «main» вместо «lib»;потому что вы называете эту версию конструктора.Как правило, вам все равно, какая версия конструктора вызывается ... они должны быть одинаковыми, но вы нарушили это требование.

Это важная часть: ваш код теперь не работает.В ваших функциях библиотеки вы ожидаете увидеть этот массив char в TImage<char> , но конструктор никогда его не создаст. Далее, представьте, что вы говорите new TImage<char> в main и передаете указательк функции в вашей библиотеке, где он будет удален.main выделяет один байт пространства, библиотечная функция пытается освободить десять.Или, если ваш CreateImageChar метод вернул TImage<char>, который он создает ... main выделит один байт в стеке для возвращаемого значения, а CreateImageChar заполнит его десятью байтами данных.И так далее.

0 голосов
/ 29 июля 2011

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

0 голосов
/ 29 июля 2011

Структура памяти - это концепция времени компиляции;это не имеет ничего общего с компоновщиком.Функция main считает, что TImage меньше, чем функции CreateImage..., потому что она была скомпилирована с другой версией TImage.

Если функция CreateImage... была определена в заголовке как встроенные функции, онистанет частью модуля компиляции main.cpp и, таким образом, сообщит о тех же характеристиках размера, что и main report.

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

EDIT: Я только что заметил, что третья строка cout не содержит "Created (lib), 10").Предполагая, что это не опечатка, я подозреваю, что происходит то, что CreateImageChar не встраивает вызов в конструктор и поэтому использует версию main.cpp.

0 голосов
/ 29 июля 2011

Компилятор всегда будет создавать экземпляр вашего шаблона при его использовании - если определение доступно.

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

Теперь,при связывании может возникнуть ситуация, которая обычно недопустима: более одного определения для каждого класса / функции / метода.Для шаблонов это специально разрешено, и компилятор выберет одно определение для вас.Это то, что происходит в вашем случае, и то, что вы называете «переопределением».

...