为什么CRTP实现和接口方法命名不同?

时间:2013-01-31 12:27:57

标签: 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;
    }

};

即,接口名称和实现方法不同。现在,我通常不希望从外部可以看到实现方法,这需要上面的朋友声明,并且在多级层次结构中证明是一个主要的kludge(即使使用描述here的技巧)。

现在,我想出了以下内容:

// 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,无论......),我得到一个无限循环,所以它可能会产生那种错误因为它们没有在编译时被捕获,所以有点难以找到。除此之外,它几乎就像普通的虚函数一样简单,而且我不必为隐藏实现方法而斗争。它似乎简单而优雅,但似乎没有人使用它...我的问题是,那么,捕获的是什么?为什么不采用这种方法?隐藏实施方法根本不是什么大不了的事吗?还是潜伏在那里的某种无法估量的邪恶的邪恶势力,在我尝试这种方法的时候准备吞噬我的灵魂?

2 个答案:

答案 0 :(得分:5)

在CRTP背后隐藏着“邪恶的力量”,除了您提到的具有同等命名的界面功能及其实现的问题,但在我看来,这有点像“寻找麻烦”。

至于问题“为什么不使用这种方法?”,我认为不是这样的。这种方法在需要时被广泛使用;但没有任何设计模式可以理解所有时间。在某些特定的设计情况下,每个都很方便,CRTP也不例外。

当你有几个不相关的类支持常见接口,但实现轻微时,CRTP最有用强>(不完全)不同。如果任何这些单词我用粗体字表示并不描述你的设计用例,可能CRTP没有意义:

  • “几个”:如果你只有一个这样的类而不是很多,为什么要将它的接口分解为一个超类呢?它只是介绍了一些冗余的命名约定;
  • “Unrelated”:如果你的类是相关的,这意味着它们来自一个公共基类,因为需要运行时多态性(这种情况经常发生,就像你想拥有它一样) 异构对象集合),然后将公共接口纳入该基类通常很自然;
  • “Common”:如果您的类不共享相当广泛的公共接口,则没有太多因素可以分解为基类。例如,如果所有类具有共同的size()方法,那么创建基类只是为了保存该方法可能是一个无用的细粒度的因子分析;
  • “稍微”:如果您的接口是以完全不同的方式实现的,这意味着没有共同的实现,那么它就不会有意义的是创建一个简单地将所有函数调用转发给子类的基类。做什么的?

但是,如果您的设计情况是上述四个属性的全部,那么您肯定有CRTP的用例:没有虚函数开销,编译器可以完全优化您的代码,您可以清楚地分离接口和实现,通过捕获常见的实现逻辑来实现最小的冗余,等等。

但是,您可能会发现这种情况并非 常见。希望这能回答你的问题。

答案 1 :(得分:4)

你几乎说明了原因:未能实现该功能会导致无限循环。

通过将接口与实现分离,当派生类不提供实现时,它允许发生两件事

1)如果基类具有自己的实现,则它的行为类似于普通的虚函数,其中基类具有默认实现。
2)如果基类没有提供实现,则无法编译。同样,这类似于纯虚函数。

最后,有一些(例如Herb Sutter)建议在使用虚拟方法时始终将接口(公共函数)与实现(私有函数)分开。给http://www.gotw.ca/publications/mill18.htm一个读数。通过将分离作为CRTP的一部分进行,您将获得相同的好处。