Шаблонная функция C ++ компилируется в заголовке, но не в реализации

Указатель NULL - это тот, который указывает на никуда. Когда вы разыскиваете указатель p, вы говорите «дайте мне данные в месте, хранящемся в« p ». Когда p является нулевым указателем, местоположение, хранящееся в p, является nowhere, вы говорите «Дайте мне данные в месте« нигде ». Очевидно, он не может этого сделать, поэтому он выбрасывает NULL pointer exception.

В общем, это потому, что что-то не было правильно инициализировано.

26
задан Peter Mortensen 14 August 2010 в 02:22
поделиться

6 ответов

Проблема, с которой вы столкнулись, заключается в том, что компилятор не знает, какие версии вашего шаблона создать. Когда вы перемещаете реализацию своей функции в x.cpp, она находится в другой единице перевода, чем main.cpp, и main.cpp не может ссылаться на конкретный экземпляр, потому что он не существует в этом контексте. Это хорошо известная проблема с шаблонами C ++.Есть несколько решений:

1) Просто поместите определения прямо в файл .h, как вы делали раньше. У этого есть свои плюсы и минусы, в том числе решение проблемы (за), что, возможно, делает код менее читаемым, а на некоторых компиляторах труднее отлаживать (против) и, возможно, увеличивает раздувание кода (против).

2) Поместите реализацию в x.cpp и #include "x.cpp" из x.h . Если это кажется странным и неправильным, просто имейте в виду, что #include не делает ничего, кроме чтения указанного файла и его компиляции , как если бы этот файл был частью x.cpp Другими словами, это именно то, что делает решение №1 выше, но хранит их в отдельных физических файлах. При выполнении подобных действий очень важно не пытаться скомпилировать файл #include d самостоятельно. По этой причине я обычно даю таким файлам расширение hpp , чтобы отличать их от файлов h и файлов cpp .

Файл: dumper2.h

#include <iostream>
#include <string>
#include <vector>

void test();
template <class T> void dumpVector( std::vector<T> v,std::string sep);
#include "dumper2.hpp"

Файл: dumper2.hpp

template <class T> void dumpVector(std::vector<T> v, std::string sep) {
  typename std::vector<T>::iterator vi;

  vi = v.begin();
  std::cout << *vi;
  vi++;
  for (;vi<v.end();vi++) {
    std::cout << sep << *vi ;
  }
  std::cout << "\n";
  return;

}

3) Поскольку проблема в том, что конкретный экземпляр dumpVector не известен модулю трансляции, который пытается его использовать. , вы можете принудительно создать его конкретный экземпляр в той же единице перевода, где определен шаблон. Просто добавив это: template void dumpVector (std :: vector v, std :: string sep); ... в файл, в котором определен шаблон.При этом вам больше не нужно # включать файл hpp из файла h :

Файл: dumper2.h

#include <iostream>
#include <string>
#include <vector>

void test();
template <class T> void dumpVector( std::vector<T> v,std::string sep);

Файл: dumper2.cpp

template <class T> void dumpVector(std::vector<T> v, std::string sep) {
  typename std::vector<T>::iterator vi;

  vi = v.begin();
  std::cout << *vi;
  vi++;
  for (;vi<v.end();vi++) {
    std::cout << sep << *vi ;
  }
  std::cout << "\n";
  return;
}

template void dumpVector<int>(std::vector<int> v, std::string sep);

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

36
ответ дан 28 November 2019 в 06:57
поделиться

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

2
ответ дан Tim Kay 14 August 2010 в 02:22
поделиться

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

Что касается того, почему другие компиляторы (включая gcc) не реализовали его, причина в том, что довольно просто: потому что экспорт чрезвычайно сложно реализовать правильно. Код внутри шаблона может (почти) полностью изменить значение в зависимости от типа, по которому создается экземпляр шаблона, поэтому вы можете не генерировать обычный объектный файл результата компиляции шаблона. Например, x + y может компилироваться в собственный код, такой как mov eax, x / add eax, y , когда создается на основе int , но компилируется в вызов функции, если создается на основе чего-то вроде std :: string , что ov оператор erloads + .

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

Из-за чрезмерных усилий, отсутствия реализации и т. Д. Комитет проголосовал за удаление export из следующей версии стандарта C ++. Были сделаны два других, довольно разных предложения (модули и концепции), каждое из которых обеспечивало по крайней мере часть того, для чего был предназначен export , но способами, которые (по крайней мере, надеялись быть) более полезными. и разумно реализовать.

10
ответ дан 28 November 2019 в 06:57
поделиться

Параметры шаблона разрешаются как время компиляции.

Компилятор находит .h, находит подходящее определение для dumpVector и сохраняет его. Компиляция этого .h завершена. Затем он продолжает анализировать файлы и компилировать файлы. Когда он читает реализацию dumpVector в .cpp, он компилирует совершенно другой модуль. Ничто не пытается создать экземпляр шаблона в dumper2.cpp, поэтому код шаблона просто пропускается. Компилятор не будет пробовать все возможные типы шаблона, надеясь, что позже для компоновщика будет что-то полезное.

Затем, во время компоновки, реализация dumpVector для типа int не была скомпилирована, поэтому компоновщик ее не найдет. Следовательно, почему вы видите эту ошибку.

Ключевое слово export предназначено для решения этой проблемы, к сожалению, немногие компиляторы поддерживают его. Так что сохраните свою реализацию в том же файле, что и ваше определение.

5
ответ дан 28 November 2019 в 06:57
поделиться

Именно так шаблоны работают в C ++, вы должны указать реализация в шапке.

Когда вы объявляете / определяете функцию-шаблон, компилятор не может волшебным образом узнать, с какими конкретными типами вы, возможно, захотите использовать шаблон, поэтому он не может сгенерировать код для помещения в файл .o, как это было бы с нормальная функция. Вместо этого он полагается на создание конкретного экземпляра для типа, когда видит использование этого экземпляра.

Итак, когда реализация находится в файле .C, компилятор в основном говорит: «Эй, у этого шаблона нет пользователей, не генерируйте никакого кода». Когда шаблон находится в заголовке, компилятор может увидеть использование в main и фактически сгенерировать соответствующий код шаблона.

1
ответ дан 28 November 2019 в 06:57
поделиться

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

See also:

http://www.parashift.com/c++-faq-lite/templates.html#faq-35.12

http://www.parashift.com/c++-faq-lite/templates.html#faq-35.14

0
ответ дан 28 November 2019 в 06:57
поделиться
Другие вопросы по тегам:

Похожие вопросы: