【问题标题】:Template specialization of a single method from a templated class模板化类中单个方法的模板特化
【发布时间】:2010-12-15 23:07:27
【问题描述】:

始终考虑到包含我的模板类的以下标头包含在至少两个 .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?代码不应在每次使用时重复。我搜索了谷歌,在 SO 中阅读了一些问题,并尝试了许多建议的解决方案,但没有一个成功构建(至少在 VS 2008 中没有)。

谢谢!

【问题讨论】:

  • 为什么要删除内联?你觉得它在美学上令人不快吗?你认为它会改变你代码的含义吗?
  • 因为如果这个方法很“长”并且在很多地方使用,我会得到它的二进制代码复制到任何地方,对吗?我试图在问题中解释这一点,但我想不清楚...... :)
  • @Martin:如果实现需要大量其他代码,然后必须包含在此标头而不是 cpp 文件中怎么办?

标签: c++ visual-studio-2008 templates specialization


【解决方案1】:

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

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。这样就成功了!
  • 这同样适用于无模板函数(与类方法相反)。我的函数专业化遇到了相同的链接器错误。我将函数专业化的主体移动到一个 .cpp 文件中,并将专业化的声明留在标题中,一切正常。谢谢!
  • 我刚遇到这个问题,上面的解决了。此外,您需要注意编译器扩展模板代码的位置。如果执行两次,编译器会报错多个定义。
【解决方案2】:

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

【讨论】:

    【解决方案3】:

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

    【讨论】:

    • 从问题评论中复制:因为如果这个方法是“长的”并且在很多地方使用,我会得到它的二进制代码复制到任何地方,对吧?我试图在问题中解释这一点,但我想这并不清楚...... :)
    • 没有。链接器会删除任何额外的副本。因此,在应用程序或库中,您将只有一次该方法的实例。
    • 如果inline 关键字导致函数被实际内联(标准规定编译器应将其作为提示),则无法删除这些额外的副本。然而,它只是内联的提示(它的主要作用是说“不要以特定方式在链接冲突中产生错误”)
    【解决方案4】:

    如果您出于某种原因想要删除内联,maxim1000 的解决方案是完全有效的。

    不过,在您的评论中,您似乎认为 inline 关键字意味着包含其所有内容的函数始终被内联,但 AFAIK 实际上非常依赖于您的编译器优化。

    引用C++ FAQ

    有几种方法可以指定函数是内联的,其中一些 其中涉及 inline 关键字,其他不涉及。不管你怎么 将函数指定为内联,这是编译器的请求 允许忽略:编译器可能内联扩展一些、全部或无 您调用指定为内联函数的位置。 (别 如果这似乎无可救药地含糊不清,请气馁。的灵活性 上面实际上是一个巨大的优势:它让编译器处理大 功能与小功能不同,而且它可以让编译器 如果选择正确的编译器,生成易于调试的代码 选项。)

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

    【讨论】:

      【解决方案5】:

      这有点过时了,但我想我会把它留在这里以防它帮助其他人。我在谷歌上搜索导致我来到这里的模板专业化,虽然 @maxim1000 的回答是正确的,并最终帮助我解决了我的问题,但我认为它并不十分清楚。

      我的情况与 OP 的情况有些不同(但我认为足以留下这个答案)。基本上,我使用的是第三方库,其中包含定义“状态类型”的所有不同类型的类。这些类型的核心只是enums,但这些类都继承自一个公共(抽象)父类并提供不同的实用函数,例如运算符重载和static toString(enum type) 函数。每个状态enum 彼此不同且不相关。例如,一个enumNORMAL, DEGRADED, INOPERABLE 字段,另一个有AVAILBLE, PENDING, MISSING 等。我的软件负责管理不同组件的不同类型的状态。我想为这些enum 类使用toString 函数,但由于它们是抽象的,我无法直接实例化它们。我可以扩展我想使用的每个类,但最终我决定创建一个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
      

      不知道这是否是解决我的问题的理想解决方案,但它对我有用。现在,无论我最终使用了多少枚举类型,我所要做的就是在 .cpp 文件中为 toString 方法添加几行,我可以使用库已经定义的 toString 方法而无需实现它自己而不是扩展我想使用的每个 enum 类。

      【讨论】:

        【解决方案6】:

        我想补充一点,如果您打算在头文件中也保留特化,仍然有充分的理由保留 inline 关键字。

        “直观地说,当你完全特化某个东西时,它不再依赖于模板参数——所以除非你将特化内联,你需要把它放在一个 .cpp 文件而不是一个 .h 或者你最终违反了单一定义规则......”

        参考:https://stackoverflow.com/a/4445772/1294184

        【讨论】:

          猜你喜欢
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 2020-12-24
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          • 1970-01-01
          相关资源
          最近更新 更多