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


92

Завжди враховуючи, що наступний заголовок, що містить мій шаблонний клас, міститься щонайменше у двох .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).

Дякую!


4
Чому ви хочете видалити вбудований? Ви вважаєте це естетично неприємним? Чи вважаєте ви, що це змінює значення вашого коду?
Мартін Йорк,

1
Тому що якби цей метод був би "довгим" і використовувався б у багатьох місцях, я б отримував його двійковий код скрізь, правда? Я намагався пояснити це у запитанні, але, мабуть, це було незрозуміло ... :)
Chuim

@Martin: Що робити, якщо реалізації потрібно багато іншого коду, який потім повинен бути включений цим заголовком замість файлу cpp?
sbi

Відповіді:


72

Як і у простих функціях, ви можете використовувати декларацію та реалізацію. Введіть у декларацію заголовка:

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
}

Не забудьте видалити вбудований (я забув і думав, що це рішення не буде працювати :)). Перевірено на VC ++ 2005


Раніше я пробував щось принаймні схоже на це, але у мене виникали інші помилки, але тепер, коли ви згадали, я, мабуть, забув видалити inlineкопію / вставку. Таким чином це спрацювало!
Chuim

Те саме стосується безкоштовних функцій шаблону (на відміну від методів класів). Я отримував ту ж помилку компонувальника для моєї спеціалізації функцій. Я перемістив тіло спеціалізації функції у файл .cpp і залишив декларацію про спеціалізацію в заголовку, і все запрацювало. Дякую!
aldo

Я просто зіткнувся з цією проблемою, і вищевказане вирішило її для мене. Крім того, вам слід подбати про те, де компілятор розширює код шаблону. Якщо це робиться двічі, компілятор скаржиться на кілька визначень.
Diederik

4

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


3

Немає підстав видаляти ключове слово inline.
Це все одно не змінює значення коду.


Скопійовано з коментаря до запитання: Тому що якби цей метод був би "довгим" і використовувався в багатьох місцях, я б отримував його двійковий код скрізь, правда? Я намагався пояснити це у запитанні, але, мабуть, це було незрозуміло ... :)
Chuim

1
Ні. Лінкер видаляє всі зайві копії. Отже, у програмі або бібліотеці ви мали б лише один раз екземпляр методу.
Мартін Йорк,

3
Якщо inlineключове слово призводить до того, що функція фактично вбудована (стандарт говорить, що компілятор повинен сприймати це як підказку), то ці зайві копії не можна видалити. Однак це лише натяк на вбудовування (його основний ефект полягає в тому, щоб сказати "не генерувати помилок при зіткненнях посилань певним чином")
Якк - Адам Неврамон

2

Якщо ви хочете видалити вбудований з будь-якої причини, рішення maxim1000 є цілком дійсним.

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

Посилання з поширених запитань на C ++

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

Отже, якщо ви не знаєте, що ця функція насправді роздує ваш виконуваний файл або якщо ви не хочете видалити його з заголовка визначення шаблону з інших причин, ви можете насправді залишити його там, де вона є, без шкоди


1

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

"Інтуїтивно зрозуміло, що коли ви повністю спеціалізуєтесь на чомусь, це вже не залежить від параметра шаблону - тому, якщо ви не зробите спеціалізацію вбудованою, вам потрібно помістити її у файл .cpp замість .h або в кінцевому підсумку порушити єдине правило визначення ... "

Довідково: https://stackoverflow.com/a/4445772/1294184


0

Це трохи ЗЗ, але я думав залишити це тут на випадок, якщо це допоможе комусь іншому. Я гуглив про спеціалізацію шаблонів, яка привела мене сюди, і хоча відповідь @ maxim1000 була правильною і врешті-решт допомогла мені розібратися у своїх проблемах, я не думав, що це було до кінця зрозумілим.

Моя ситуація дещо інша (але, схоже, достатня, щоб залишити цю відповідь), ніж OP. В основному, я використовую сторонню бібліотеку з усіма різними типами класів, які визначають "типи стану". Серцем цих типів є просто enums, але всі класи успадковуються від загального (абстрактного) батька і забезпечують різні функції утиліти, такі як перевантаження оператора та static toString(enum type)функція. Кожен статус enumвідрізняється один від одного і не пов’язаний. Наприклад, у одного enumє поля NORMAL, DEGRADED, INOPERABLE, у іншого є AVAILBLE, PENDING, MISSINGтощо. Моє програмне забезпечення відповідає за управління різними типами статусів для різних компонентів. Вийшло так, що я хотів використати toStringдля них функціїenumкласи, але оскільки вони абстрактні, я не міг створити їх безпосередньо. Я міг розширити кожен клас, який хотів використовувати, але врешті-решт вирішив створити templateклас, де це typenameбув би той, який конкретний статус enumя турбував. Можливо, про це рішення можна провести дебати, але я відчув, що це набагато менша робота, ніж розширення кожного абстрактного enumкласу власним власним та реалізація абстрактних функцій. І, звичайно, у своєму коді я просто хотів мати можливість зателефонувати .toString(enum type)і надрукувати це рядкове представлення цього enum. Оскільки всі enums були абсолютно не пов'язані між собою, у кожного з них були свої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клас, який я хочу використовувати.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.