为什么 CRTP 实现和接口方法的名称不同
Why are CRTP implementation and interface methods named differently?
在我读到的关于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;
}
};
也就是说,接口的名称和实现方法不同。现在,我通常不希望实现方法从外部可见,这需要上面的朋友声明,并且在多级层次结构中被证明是一个主要的麻烦(即使使用此处描述的技巧)。
现在,我想出了以下内容:
// 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;
现在,call_foo(b)
就像我期望的那样工作,调用 B 的 foo() 实现。同样,call_foo(c)
也按预期工作(因为它没有...由于显而易见的原因,它陷入了无限循环)。我可以看到的一个缺点是,如果我忘记在派生类中实现一个方法(或者拼写错误,忘记将其限定为 const,等等),我会得到一个无限循环,所以它可能会使这种错误更难找到,因为它们没有在编译时被捕获。除此之外,它几乎就像普通的虚拟函数一样简单,我不必与隐藏实现方法作斗争。它看起来简单而优雅,但似乎没有人使用它......那么,我的问题是,有什么收获?为什么不使用这种方法?隐藏实现方法根本不是什么大问题吗?还是有某种不可估量的邪恶力量潜伏在那里,准备在我在一个真正的项目中尝试这种方法的那一刻吞噬我的灵魂?
CRTP 背后没有潜伏着"邪恶力量",除了您提到的同名接口函数及其实现的问题,但在我看来,这有点像"自找麻烦"。
至于"为什么不使用这种方法?"这个问题,我认为情况并非如此。这种方法在需要时被广泛使用;但是没有一种设计模式总是有意义的。每种方法在某些特定的设计情况下都派上用场,CRTP 也不例外。
当您有多个不相关的类支持通用接口但实现方式略有(不完全)不同时,CRTP 最有用。如果我用粗体输入的这些词中的任何一个没有描述您的设计用例,那么 CRTP 可能没有意义:
- "几个":如果你只有一个这样的类而不是很多,为什么要把它的接口分解成一个超类?它只是引入了一些冗余的命名约定;
- "不相关":如果您的类是相关的,这意味着它们派生自公共基类,因为需要运行时多态性(这种情况很常见,例如,如果您想要一个异构的对象集合),那么将公共接口分解到该基类中通常是很自然的;
-
"Common":如果您的类不共享一个相当广泛的公共接口,那么基类中就没有太多因素可以分解。例如,如果所有类只有一个共同的
size()
方法,那么创建一个基类来保存该方法很可能是一个无用的精细分解粒度; - "稍微":如果你的接口是以完全不同的方式实现的,这意味着没有共同的实现,那么创建一个简单地将所有函数调用转发到子类的基类是没有意义的。为什么?
但是,当您的设计情况使上述所有四个属性都适用时,那么您肯定有一个 CRTP 用例:没有虚拟函数开销,编译器可以完全优化您的代码,接口和实现的清晰分离,通过捕获公共实现逻辑实现最小冗余,等等。
但是,您可能会意识到这种情况并不常见。希望这能回答您的问题。
你几乎说明了原因:未能实现该函数会导致无限循环。
通过将接口与实现分离,当派生类不提供实现时,它允许发生两件事
1)如果基类有自己的实现,它的行为就像一个普通的虚函数,其中基类有一个默认的实现。
2) 如果基类不提供实现,则编译失败。 同样,这类似于纯虚函数。
最后,有些人(如Herb Sutter)建议在使用虚拟方法时始终将接口(公共函数)与实现(私有函数)分开。 给 http://www.gotw.ca/publications/mill18.htm 读一读。 通过将分离作为 CRTP 的一部分执行,您可以获得相同的好处。
- 为不同配置设置MSVC_RUNTIME_LIBRARY的正确方法是什么
- 通过方法访问结构
- 最小硬币更换问题(自上而下方法)
- C++为构建时间获取QDateTime的可靠方法
- 在C#中处理C++指针而不使用unsafe的最佳方法
- CRTP:为什么获得嵌套类型和派生类的嵌套方法有区别
- 防止意外隐藏(CRTP mixin提供的方法)
- 有没有一种干净(更)的方法将 CRTP 与可变参数继承混合在一起?
- std::d eclval vs crtp,无法从不完整类型推断方法返回类型
- CRTP-我可以做私人方法吗?
- CRTP:基于派生类内容的基类启用方法
- CRTP - 静态接口中的"abstract"方法
- 在这种情况下,我如何使用CRTP删除虚拟方法
- CRTP和返回void的方法*
- 在 C++11 中使用 CRTP 作为抽象静态方法的替代方法
- CRTP - 如何从派生类调用方法的基类实现
- 具有派生类重载方法的 CRTP
- CRTP 向下转换方法调用
- 使用CRTP的静态多态性:使用基类调用派生方法
- 为什么 CRTP 实现和接口方法的名称不同