Шаблонная специализация одного метода из шаблонного класса - PullRequest
79 голосов
/ 12 ноября 2009

Всегда учитывая, что следующий заголовок, содержащий мой шаблонный класс, включен как минимум в два .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
}

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

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

Спасибо!

Ответы [ 5 ]

63 голосов
/ 12 ноября 2009

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

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

4 голосов
/ 12 ноября 2009

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

1 голос
/ 06 июля 2015

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

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

Цитирование из C ++ FAQ

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

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

1 голос
/ 12 ноября 2009

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

0 голосов
/ 12 января 2019

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

Моя ситуация немного отличается (но я думаю, что она достаточно похожа, чтобы оставить этот ответ), чем у ОП. По сути, я использую стороннюю библиотеку со всеми видами классов, которые определяют «типы статуса». Сердцем этих типов являются просто enum s, но все классы наследуются от общего (абстрактного) родителя и предоставляют различные вспомогательные функции, такие как перегрузка операторов и функция 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.

Это (очень упрощенный) заголовочный файл для enum s. В действительности каждый класс 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 класса, который я хочу использовать.

...