2017-07-18 105 views
21

考虑以下标准CRTP例如:保护CRTP模式调用

#include <iostream> 

template<class Derived> 
struct Base { 
    void f() { static_cast<Derived *>(this)->f(); } 
    void g() { static_cast<Derived *>(this)->g(); } 
}; 

struct Foo : public Base<Foo> { 
    void f() { std::cout << 42 << std::endl; } 
}; 

int main() { 
    Foo foo; 
    foo.f(); // just OK 
    foo.g(); // this will stack overflow and segfault 
} 

如果这是常规虚拟继承我能有标记的虚拟fg方法为纯像

struct Base { 
    virtual void f() = 0; 
    virtual void g() = 0; 
}; 

并得到关于Foo抽象的编译时错误。但是CRTP没有提供这种保护。我能以某种方式实施它吗?运行时检查也是可以接受的。我想过比较this->f指针与static_cast<Derived *>(this)->f,但没有设法使其工作。

+2

我不知道这是否是标准定义的行为,但是您可以在'Base :: g'内'static_assert'指定'&Derived :: g!=&Base :: g'。 – Holt

+6

这似乎也工作:http://coliru.stacked-crooked.com/a/80021ad0bfb7aa47 –

+0

@ JohannesSchaub-litb这很聪明!你应该写一个答案。 – Yakk

回答

12

这里是另一种可能性:

#include <iostream> 

template<class Derived> 
struct Base { 
    auto f() { return static_cast<Derived *>(this)->f(); } 
    auto g() { return static_cast<Derived *>(this)->g(); } 
}; 

struct Foo : public Base<Foo> { 
    void f() { std::cout << 42 << std::endl; } 
}; 

int main() { 
    Foo foo; 
    foo.f(); // just OK 
    foo.g(); // this will not compile 
} 

对于GCC,在扣除'auto'“”之前,它给出了一个非常明确的错误信息(“错误:使用'auto Base :: g()[with Derived = Foo]''),而对于Clang,可读性稍差,无限次重复唱歌模板实例Base<Foo>::g,与g实例化自己,但最终以错误结束。

+1

这实际上很少见,那是根据我的经验,clang给出的错误信息比gcc差。 – Yakk

+3

叮当的最新版本[似乎产生](https://godbolt.org/g/ozbnVB)接近gcc(clang> = 3.9.1)。 – Holt

+2

@霍尔特宇宙已经恢复秩序。 – Yakk

23

您可以在编译时,这两个指针成员函数是不同的,例如:

template<class Derived> 
struct Base { 
    void g() { 
     static_assert(&Derived::g != &Base<Derived>::g, 
         "Derived classes must implement g()."); 
     static_cast<Derived *>(this)->g(); 
    } 
}; 
+2

你不确定这是否是上述评论中的标准定义行为。在这个答案中关心这个问题呢? – Yakk

+1

@Yakk我不确定指向基类/派生类之间成员函数的规则(转换/比较)。我检查了标准(特别是[conv.mem#2])和一些关于SO的问题,我相当确信这是有效的。但如果你有理由相信这不是,请随时解释 - 如果这是不正确的,我会很快删除这个答案。 – Holt

0

断言你可以考虑做这样的事情吧。您可以Derived一员,无论是每次提供其作为直接模板参数实例化一个Base或者使用一个类型别名因为我在这个例子中所做的:

template<class Derived> 
struct Base { 
    void f() { d.f(); } 
    void g() { d.g(); } 
private: 
    Derived d; 
}; 

struct FooImpl { 
    void f() { std::cout << 42 << std::endl; } 
}; 

using Foo = Base<FooImpl>; 

int main() { 
    Foo foo; 
    foo.f(); // OK 
    foo.g(); // compile time error 
} 

当然Derived不再是衍生所以你可能会选择一个更好的名字。

+3

这个名称并不像修改后的语义那样糟糕。不能用'Derived'代替'Base '。所以通用函数a-la' template void foo(Base &){}'不再有效。 – StoryTeller

+0

@StoryTeller它并不假装在语义上是相同的。它被标记为可能的(安全的)替代方案,在很多情况下都很好。(我一直都在使用它) – Galik

12

您可以使用此解决方案,你可以有纯粹的“非虚拟抽象”的功能,并尽可能多地映射到CRTP这个recommendation of H. Sutter

template<class Derived> 
struct Base 
    { 
    void f(){static_cast<Derived*>(this)->do_f();} 
    void g(){static_cast<Derived*>(this)->do_g();} 

    private: 
    //Derived must implement do_f 
    void do_f()=delete; 
    //do_g as a default implementation 
    void do_g(){} 
    }; 

struct derived 
    :Base<derived> 
    { 
    friend struct Base<derived>; 

    private: 
    void do_f(){} 
    };