模板化类中单个方法的模板专业化


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

Answers:


71

与简单函数一样,您可以使用声明和实现。放入您的标头声明:

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上检查


之前我确实尝试过至少与此类似的操作,但是我遇到了其他错误,但是现在您提到我一定忘记了删除inlinewhile复制/粘贴操作。这样工作!
Chuim

这同样适用于没有模板的函数(与类方法相反)。我的函数专门化遇到了相同的链接器错误。我将功能专业化的主体移到了.cpp文件中,并将该专业化的声明留在了标头中,一切正常。谢谢!
aldo 2012年

我只是遇到了这个问题,上面的问题为我解决了。此外,您需要注意编译器在何处扩展模板代码。如果执行两次,则编译器会抱怨多个定义。
Diederik 2013年

4

您需要将专业化定义移至CPP文件。即使未将函数声明为模板,也可以对模板类的成员函数进行特殊化。


3

没有理由删除内联关键字。
无论如何,它不会改变代码的含义。


从问题注释中复制:因为如果此方法是“ long”的并且在很多地方使用,我会得到二进制代码复制到各处,对吗?我试图在问题中解释这一点,但我想还不清楚... :)
Chuim

1
否。链接器删除所有多余的副本。因此,在应用程序或lib中,该方法的实例只有一次。
马丁·约克

3
如果inline关键字导致该函数实际内联(标准说编译器应将其作为提示),则这些多余的副本将无法删除。但是,它只是内联的一个提示(其主要作用是“不要以特定方式在链接冲突中生成错误”)
Yakk-Adam Nevraumont

2

如果出于任何原因要删除内联,maxim1000的解决方案都是完全有效的。

不过,在您的评论中,您似乎似乎认为inline关键字意味着始终将内联函数及其所有内容内联,但是AFAIK实际上很大程度上取决于编译器的优化。

引用C ++常见问题解答

有几种方法可以指定一个函数为内联,其中一些涉及inline关键字,而其他则不涉及。无论您如何将函数指定为内联函数,都要求编译器忽略该请求:编译器可能会内联扩展您调用被指定为内联函数的位置的全部,部分或全部。(不要灰心,因为这似乎望尘莫及。上述的灵活性实际上是一个巨大的优势:它可以使编译器将大型函数与小型函数区别对待,另外,如果选择了,编译器可以生成易于调试的代码。正确的编译器选项。)

因此,除非您知道该函数实际上会使可执行文件膨胀,否则除非您出于其他原因要从模板定义标头中删除该函数,否则实际上可以将其保留在任何位置而不会造成任何损害


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的解决方案有点不同。

这是的头文件(已大大简化)enum。实际上,每个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我要使用的课程。

By using our site, you acknowledge that you have read and understand our Cookie Policy and Privacy Policy.
Licensed under cc by-sa 3.0 with attribution required.