Специализация шаблона одного метода из шаблонного класса

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

template <class T>
class TClass 
{
public:
  void doSomething(std::vector<T> * v);
};

template <class T>
void TClass<T>::doSomething(std::vector<T> * v) {
  // Do something with a vector of a generic T
}

template <>
inline void TClass<int>::doSomething(std::vector<int> * v) {
  // Do something with a vector of int's
}

Но обратите внимание на inline в методе специализации. Это необходимо, чтобы избежать ошибки компоновщика (в VS2008 это LNK2005) из-за того, что метод определен более одного раза. Я понимаю это, потому что AFAIK полная специализация шаблона совпадает с простым определением метода.

Итак, как мне удалить это inline? Код не должен дублироваться при каждом его использовании. Я искал в Google, читал несколько вопросов здесь, в SO, и пробовал многие из предложенных решений, но ни одно из них не было успешно построено (по крайней мере, не в VS 2008).

Спасибо!


person Chuim    schedule 12.11.2009    source источник
comment
Почему вы хотите удалить встроенный? Вам это не нравится с эстетической точки зрения? Как вы думаете, это меняет смысл вашего кода?   -  person Martin York    schedule 12.11.2009
comment
Потому что, если бы этот метод был длинным и использовался во многих местах, я бы везде копировал его двоичный код, верно? Я попытался объяснить это в вопросе, но, думаю, непонятно ... :)   -  person Chuim    schedule 12.11.2009
comment
@Martin: Что делать, если реализации требуется много другого кода, который затем должен быть включен в этот заголовок вместо файла cpp?   -  person sbi    schedule 13.11.2009


Ответы (6)


Как и в случае с простыми функциями, вы можете использовать объявление и реализацию. Поместите в свое объявление заголовка:

template <>
void TClass<int>::doSomething(std::vector<int> * v);

и поместите реализацию в один из ваших cpp-файлов:

template <>
void TClass<int>::doSomething(std::vector<int> * v) {
 // Do somtehing with a vector of int's
}

Не забудьте удалить inline (я забыл и подумал, что это решение не сработает :)). Проверено на VC ++ 2005

person maxim1000    schedule 12.11.2009
comment
Я пробовал что-то вроде этого раньше, но у меня были другие ошибки, но теперь, когда вы упомянули, я, должно быть, забыл удалить inline при копировании / вставке. Вот так это сработало! - person Chuim; 12.11.2009
comment
То же самое относится к функциям без шаблонов (в отличие от методов класса). Я получал ту же ошибку компоновщика для моей специализации функции. Я переместил тело специализации функции в файл .cpp и оставил объявление специализации в заголовке, и все заработало. Спасибо! - person aldo; 12.04.2012
comment
Я только что столкнулся с этой проблемой, и это решило ее за меня. Кроме того, вам нужно позаботиться о том, где компилятор разворачивает код шаблона. Если это делается дважды, компилятор жалуется на несколько определений. - person Diederik; 03.07.2013

Вам необходимо переместить определение специализации в файл CPP. Специализация функции-члена шаблонного класса разрешена, даже если функция не объявлена ​​как шаблон.

person BostonLogan    schedule 12.11.2009

Нет причин удалять ключевое слово inline.
Это никоим образом не меняет смысла кода.

person Martin York    schedule 12.11.2009
comment
Скопировано из комментария к вопросу: Потому что, если бы этот метод был длинным и использовался во многих местах, я бы скопировал его двоичный код повсюду, верно? Я попытался объяснить это в вопросе, но, думаю, непонятно ... :) - person Chuim; 12.11.2009
comment
Нет. Компоновщик удаляет все лишние копии. Таким образом, в приложении или библиотеке у вас будет только один экземпляр метода. - person Martin York; 12.11.2009
comment
Если ключевое слово inline приводит к тому, что функция фактически встроена (стандарт говорит, что компилятор должен воспринимать это как подсказку), то эти дополнительные копии не могут быть удалены. Однако это всего лишь подсказка для встраивания (его основной эффект заключается в том, чтобы не создавать ошибок при конфликтах ссылок определенным образом) - person Yakk - Adam Nevraumont; 01.11.2013

Если вы хотите удалить встроенный по какой-либо причине, решение maxim1000 вполне подходит.

Однако в вашем комментарии кажется, что вы считаете, что ключевое слово inline означает, что функция со всем его содержимым всегда встроена, но AFAIK, который на самом деле очень сильно зависит от оптимизации вашего компилятора.

Цитата из часто задаваемых вопросов по C ++

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

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

person Triskeldeian    schedule 06.07.2015

Это немного ОТ, но я подумал, что оставлю это здесь, на случай, если это поможет кому-то другому. Я искал в Google специализацию шаблонов, которая привела меня сюда, и хотя ответ @ maxim1000 верен и в конечном итоге помог мне разобраться в моих проблемах, я не думал, что это достаточно ясно.

Моя ситуация немного отличается (но достаточно похожа, чтобы оставить этот ответ, я думаю), чем у OP. По сути, я использую стороннюю библиотеку со всеми видами классов, которые определяют «типы статуса». В основе этих типов лежат простые enum, но все классы наследуются от общего (абстрактного) родителя и предоставляют различные служебные функции, такие как перегрузка оператора и функция static toString(enum type). Каждый статус enum отличается друг от друга и не связан. Например, в одном enum есть поля NORMAL, DEGRADED, INOPERABLE, в другом AVAILBLE, PENDING, MISSING и т. Д. Мое программное обеспечение отвечает за управление разными типами статусов для разных компонентов. Так получилось, что я хотел использовать toString функции для этих enum классов, но, поскольку они абстрактны, я не мог создать их экземпляры напрямую. Я мог бы расширить каждый класс, который хотел бы использовать, но в конечном итоге я решил создать template класс, где typename будет тем конкретным статусом, который enum меня заботит. Вероятно, это решение можно обсудить, но я чувствовал, что это намного меньше работы, чем расширение каждого абстрактного enum класса собственным собственным и реализация абстрактных функций. И, конечно же, в моем коде я просто хотел иметь возможность вызвать .toString(enum type) и распечатать строковое представление этого enum. Поскольку все enum не были связаны между собой, у каждой из них были свои toString функции, которые (после некоторых исследований, которые я узнал) должны были вызываться с использованием специализации шаблонов. Это привело меня сюда. Ниже приводится MCVE того, что мне пришлось сделать, чтобы это работало правильно. И на самом деле мое решение немного отличалось от @ maxim1000.

Это (значительно упрощенный) заголовочный файл для enums. На самом деле каждый enum класс был определен в собственном файле. Этот файл представляет файлы заголовков, которые предоставляются мне как часть библиотеки, которую я использую:

// file enums.h
#include <string>

class Enum1
{
public:
  enum EnumerationItem
  {
    BEARS1,
    BEARS2,
    BEARS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

class Enum2
{
public:
  enum EnumerationItem
  {
    TIGERS1,
    TIGERS2,
    TIGERS3
  };

  static std::string toString(EnumerationItem e)
  {
    // code for converting e to its string representation,
    // omitted for brevity
  }
};

добавив эту строку, чтобы разделить следующий файл на другой блок кода:

// file TemplateExample.h
#include <string>

template <typename T>
class TemplateExample
{
public:
  TemplateExample(T t);
  virtual ~TemplateExample();

  // this is the function I was most concerned about. Unlike @maxim1000's
  // answer where (s)he declared it outside the class with full template
  // parameters, I was able to keep mine declared in the class just like
  // this
  std::string toString();

private:
  T type_;
};

template <typename T>
TemplateExample<T>::TemplateExample(T t)
  : type_(t)
{

}

template <typename T>
TemplateExample<T>::~TemplateExample()
{

}

следующий файл

// file TemplateExample.cpp
#include <string>

#include "enums.h"
#include "TemplateExample.h"

// for each enum type, I specify a different toString method, and the
// correct one gets called when I call it on that type.
template <>
std::string TemplateExample<Enum1::EnumerationItem>::toString()
{
  return Enum1::toString(type_);
}

template <>
std::string TemplateExample<Enum2::EnumerationItem>::toString()
{
  return Enum2::toString(type_);
}

следующий файл

// and finally, main.cpp
#include <iostream>
#include "TemplateExample.h"
#include "enums.h"

int main()
{
  TemplateExample<Enum1::EnumerationItem> t1(Enum1::EnumerationItem::BEARS1);
  TemplateExample<Enum2::EnumerationItem> t2(Enum2::EnumerationItem::TIGERS3);

  std::cout << t1.toString() << std::endl;
  std::cout << t2.toString() << std::endl;

  return 0;
}

и это выводит:

BEARS1
TIGERS3

Понятия не имею, является ли это идеальным решением моей проблемы, но у меня оно сработало. Теперь, независимо от того, сколько типов перечисления я в конечном итоге использую, все, что мне нужно сделать, это добавить несколько строк для метода toString в файл .cpp, и я могу использовать уже определенный библиотекой метод toString, не реализуя его самостоятельно и без расширение каждого enum класса, который я хочу использовать.

person yano    schedule 12.01.2019

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

"Интуитивно понятно, что когда вы что-то полностью специализируете, это больше не зависит от параметра шаблона - поэтому, если вы не сделаете специализацию встроенной, вам нужно поместить ее в файл .cpp вместо .h, иначе вы нарушите единственное правило определения ... "

Ссылка: https://stackoverflow.com/a/4445772/1294184

person Jordan    schedule 15.11.2019