3

假设我正在尝试使用Curiously Recurring Template Pattern创建自己的 boost::filesystem::path 实现:

(为简洁起见,代码不完整,但在使用 ' ' 编译时会出现问题g++ -std=c++11 -o mypath ./mypath.cpp,使用 GCC 4.8.4)

mypath.hpp:

#ifndef MYPATH_HPP
#define MYPATH_HPP

#include <string>
#include <vector>

namespace my {

template <class T>
class PathBase
{
public:
  PathBase();
  PathBase(std::string const& p);

  std::string String() const;

  bool IsSeparator(char c) const;
  std::string Separators() const;

  typedef std::vector<std::string> pathvec;

protected:
  pathvec _path;

private:
  virtual std::string _separators() const =0;
};


class Path : public PathBase<Path>
{
public:
  Path();
  Path(std::string const& p);

private:
  virtual std::string _separators() const final;
};

} // namespace 'my'

#endif // MYPATH_HPP

我的路径.cpp:

#include "mypath.hpp"

namespace my {

//////////template class PathBase<Path>;

template<>
bool PathBase<Path>::IsSeparator(char c) const
{
  return (Separators().find(c) != std::string::npos);
}

template <>
std::string PathBase<Path>::Separators() const
{
  return _separators();
}

} // namespace

int main(int argc, char** argv)
{
  return 0;
}

当然,我发现编写的代码不会编译,因为我在隐式实例化它Separators()之后明确地专门化了它。IsSeparator()但我并不是特别想玩打地鼠的游戏,试图让我所有的方法都井井有条。

在研究关于 SO 的类似问题时,我发现其中一个接受的答案表明我可以通过仅仅声明我的专业来巧妙地解决这个问题。但...

  1. 我在 mypath.cpp 中注释掉的template class PathBase<Path>;行对问题没有影响,并且
  2. 感觉就像我的头文件已经用它的整个class Path : public PathBase<Path> { ... }声明声明了显式特化。

我的显式声明到底需要是什么样的?

4

1 回答 1

5

让我们先把这些排除在外:

  1. template class PathBase<Path>;没有声明明确的特化;它是一个明确的实例化定义。您要求编译器PathBase<Path>根据您在此之前提供的定义实例化它具有定义的所有成员。在这种特定情况下,它确实没有任何区别。

    显式特化的声明看起来像template<> class PathBase<Path>;,但这也不是您想要的;见下文。

  2. PathBase<Path>定义时的使用Path也没有声明明确的特化;根据您上面提供的定义,它会触发 的隐式实例化PathBase<Path>类模板的隐式实例化实例化类定义并且仅实例化其成员函数的声明;它不会尝试实例化函数的定义;这些仅在需要时才实例化,稍后再进行。


在您的 cpp 文件中,您明确地专门化IsSeparatorSeparators用于隐式实例化的PathBase<Path>. 您要求编译器PathBase<Path>根据您提供的通用定义进行实例化,但是,当需要这些特定函数的定义时,请使用您提供的特定定义。

当类的结构和成员的大多数通用定义都很好时,它基本上是显式特化整个类模板的简写替代方案,并且您只想微调少数成员的定义。如果您明确专门化整个类模板,则必须为专门化的所有成员函数提供单独的类定义和定义,这意味着不必要的复制粘贴。

您需要尽快告诉编译器这些显式特化,以免某些代码有可能尝试使用这些定义(它需要知道它将必须寻找特定定义而不是通用定义)。您可以通过声明(不一定定义)显式特化来做到这一点。

最安全的地方是紧接在定义的右大括号之后template <class T> class PathBase。就像是:

class Path;
template<> std::string PathBase<Path>::Separators() const;
template<> bool PathBase<Path>::IsSeparator(char c) const;

您肯定需要在头文件中执行此操作,而不是在 cpp 文件中,否则使用头文件的其他 cpp 文件将不知道显式特化,并会尝试实例化通用版本(如果需要它们)。这将使您的程序格式错误,不需要诊断(这也适用于您的示例)。这意味着:如果编译器足够聪明,可以诊断问题,你应该感激不尽;如果不是,你不能抱怨,这仍然是你的错。

事先声明了明确的特化,定义可以稍后出现,可能在单独的 cpp 文件中;没关系,就像正常功能一样。

另请注意,如果您想在头文件中包含显式特化的定义(例如,为了简化内联),您必须inline再次声明它们,就像普通函数一样。否则,在多个 cpp 文件中包含标头会使程序格式错误,NDR(通常在链接时会出现多个定义错误)。


来自[temp.expl.spec]/7的强制性标准报价:

[...] 编写专业时,请注意其位置;或者让它编译将是一种试炼,以点燃它的自焚。

是的,标准化委员会的成员也是人。

于 2016-11-27T12:14:02.413 回答