jay*_*ica 6 c++ templates naming crtp
我读到CRTP的所有地方,实际上在我编写的代码中,CTRP类层次结构如下所示:
template< class T >
class Base
{
public:
int foo_interface()
{
return static_cast< T* >(this)->foo_implementation();
}
};
class Derived : public Base< Derived >
{
friend class Base< Derived >;
int foo_implementation()
{
return 5;
}
};
Run Code Online (Sandbox Code Playgroud)
也就是说,接口的名称和实现方法是不同的.现在,我通常不希望从外部看到实现方法,这需要上面的朋友声明,并且在多级层次结构中证明是一个主要的kludge(即使使用此处描述的技巧).
现在,我想出了以下内容:
// Base class
template< class T >
class A
{
public:
int foo()
{
std::cout << "I'm in A's foo!\n";
return static_cast< T * >(this)->foo();
}
};
// Deriving class
class B : public A< B >
{
public:
int foo()
{
std::cout << "I'm in B's foo!\n";
return 5;
}
};
// Deriving class with a nasty surprise...
class C: public A< C >
{
public:
// ...crap, no foo to be found!
int bar()
{
std::cout << "I'm in C's bar!\n";
return 12;
}
};
template< class T >
int call_foo(A< T > & t)
{
return t.foo();
}
B b;
C c;
Run Code Online (Sandbox Code Playgroud)
现在,call_foo(b)就像我期望的那样工作,调用B的foo()实现.同样地,call_foo(c)也可以按预期工作(因为它不会......出于显而易见的原因它会陷入无限循环).我可以看到的一个缺点是,如果我忘记在派生类中实现一个方法(或拼错一些东西,忘记将它限定为const,无论......),我得到一个无限循环,所以它可能会产生那种错误因为它们没有在编译时被捕获,所以有点难以找到.除此之外,它几乎就像普通的虚函数一样简单,而且我不必为隐藏实现方法而斗争.它似乎简单而优雅,但似乎没有人使用它...我的问题是,那么,捕获的是什么?为什么不采用这种方法?隐藏实施方法根本不是什么大不了的事吗?或者在我真正的项目中尝试这种方法的那一刻,潜伏在那里的某种无法估量的邪恶力量准备吞噬我的灵魂吗?
在CRTP背后隐藏着"邪恶的力量",除了您提到的具有同样命名的界面功能及其实现的问题,但在我看来,这有点像"寻找麻烦".
关于"为什么不采用这种方法?"的问题.,我认为不是这样的.这种方法在需要时被广泛使用; 但没有设计模式是有道理的所有的时间.在某些特定的设计情况下,每个都很方便,CRTP也不例外.
当你有CRTP是最有用的几个,不相关的,支持一类常见的接口,但实现它略(不完全)不同.如果我用粗体字表示的这些单词中的任何一个都没有描述你的设计用例,那么可能CRTP没有意义:
size()共同的方法,那么创建一个基类只是为了保存该方法可能是一个无用的细粒度的因子分解;但是,当你的设计情况是上述所有四个属性都适用时,你肯定有一个CRTP的用例:没有虚函数开销,编译器可以完全优化你的代码,你有一个干净的界面分离和实现,您通过捕获通用实现逻辑实现最小冗余,等等.
但是,你可能会意识到,这种情况是不是常见的.希望这能回答你的问题.
您几乎已经说明了原因:未能实现该函数会导致无限循环。
通过将接口与实现分离,当派生类不提供实现时,它允许发生两种情况
1)如果基类有它自己的实现,它的行为就像一个普通的虚函数,其中基类有一个默认的实现。
2)如果基类没有提供实现,则编译失败。同样,这类似于纯虚函数。
最后,有些人(例如 Herb Sutter)建议在使用虚拟方法时始终将接口(公共函数)与实现(私有函数)分开。请阅读http://www.gotw.ca/publications/mill18.htm 。通过将分离作为 CRTP 的一部分进行,您可以获得相同的好处。