假设我正在尝试使用Curiously Recurring Template Pattern创建我自己的boost :: filesystem :: path实现:
(为简洁起见,代码不完整,但使用GCC 4.8.4时会使用'g++ -std=c++11 -o mypath ./mypath.cpp
'编译时出现问题
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
mypath.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()
。但是我并不特别想玩w鼹鼠试图让我的所有方法都顺利进行。
在研究关于SO的类似问题时,我发现其中一个accepted answer表明我可以通过仅仅宣布我的专业化来巧妙地解决这个问题。但...
template class PathBase<Path>;
行对此问题没有影响,class Path : public PathBase<Path> { ... }
声明的显式特化。 我的明确声明究竟是什么样的?
答案 0 :(得分:4)
让我们先解决这些问题:
template class PathBase<Path>;
未声明明确的专业化;它是显式实例化定义。您根据您提供的定义,请求编译器实例化PathBase<Path>
及其定义的所有成员。在这种特定情况下,它确实没有任何区别。
明确专业化的声明看起来像template<> class PathBase<Path>;
,但这不是你想要的;见下文。
在定义PathBase<Path>
时使用Path
并不会声明显式专业化;它会根据您在上面提供的定义触发PathBase<Path>
的隐式实例化。类模板的隐式实例化实例化类定义,并仅实例化其成员函数的声明;它没有尝试实例化函数的定义;这些只在需要时才会实例化,以后再进行实例化。
在您的cpp文件中,您明确地将IsSeparator
和Separators
专门用于隐式实例化的PathBase<Path>
。您根据您提供的通用定义请求编译器实例化PathBase<Path>
,但是,当需要这些特定函数的定义时,请使用您提供的特定定义。
它基本上是显式专门化整个类模板的简写替代方法,当类的结构和成员的大多数通用定义都很好时,你只想微调a的定义。很少有成员。如果您明确专门化了整个类模板,那么您必须为专业化的所有成员函数提供单独的类定义和定义,这意味着不必要的复制粘贴。
在某些代码尝试使用这些定义之前,您需要尽快告诉编译器这些显式特化(它需要知道它必须寻找特定的定义)通用的)。您可以通过声明(不一定定义)显式特化来实现。
最安全的地方是在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的强制性标准报价:
[...]在撰写专业时,请注意其位置;要么 使它编译将是一个试图点燃它的 自焚。
是的,标准化委员会的成员也是人。