【问题标题】:How to force use of curiously recurring template pattern in C++如何在 C++ 中强制使用奇怪重复的模板模式
【发布时间】:2015-05-14 07:45:28
【问题描述】:

我有以下基本模板类。

template<typename T>
class Base {
  public:
    void do_something() {
    }
};

它旨在用作奇怪地重复出现的模板模式。它应该像class B : public Base&lt;B&gt; 一样被继承。它必须class B : public Base&lt;SomeoneElse&gt; 那样被继承。我想静态地强制执行这个要求。如果有人用错了,我预计在编译阶段会出错。

我正在做的是将static_cast&lt;T const&amp;&gt;(*this) 放入do_something()。这样,继承模板的类是或继承自作为模板参数提供的类。对不起,令人困惑的表达。简单来说,它要求B 是或继承自class B : public Base&lt;SomeoneElse&gt; 中的SomeoneElse

我不知道这是否是实现这一目标的最佳方式。我觉得很恶心。

但是我想做更多。我想确保B 本身就是SomeoneElse。我该怎么做?

【问题讨论】:

  • static_cast 不强制执行任何操作;如果*this 实际上不是T,它只会导致未定义的行为。
  • @T.C.它进行静态检查。你说的应该是reinterpret_cast
  • 它只检查T 是从Base&lt;T&gt; 派生的,而不是*thisT。即,给定class Bar : public Base&lt;Bar&gt; {}; class Foo : public Base&lt;Bar&gt; {}; ,您的支票不会捕获任何东西。

标签: c++ templates inheritance crtp


【解决方案1】:

Base 的构造函数(或析构函数)设为私有,然后将T 设为friend。这样,唯一可以构造/破坏Base&lt;T&gt;的是T

【讨论】:

  • 请参考stackoverflow.com/questions/702650/…。看来你不能给模板参数加好友。
  • @Hot.PxL 该问题中票数最高的答案最后解释了如何做到这一点(friend T;,而不是friend class T;)。
  • 你是对的。但是在 C++11 不可用的情况下,有什么办法可以做到这一点?
【解决方案2】:

如果您的课程包含以下代码:

T* pT = 0;
Base *pB = pT;

如果TBase 的赋值不兼容,则会出现编译器错误。

这种检查是在 C++11 中形式化的,因此您不必手动编写它并且可以获得有用的错误消息:

#include <type_traits>

template<typename T>
class Base {

public:
    void do_something() 
    {
        static_assert(
            std::is_base_of<Base, T>::value,
            "T must be derived from Base");
    }
};

class B : public Base<B> { };

int main()
{
    B b;
    b.do_something();
}

至于确保Base 的类型参数正是派生自它的类,这在概念上似乎存在缺陷。充当基类的类不能“谈论”继承它的类型。它可能通过多重继承被多次继承,或者根本不被继承。

【讨论】:

  • 我猜它只满足等式的一方面。我还想确保B 正好是SomeoneElse
  • 这在 clang3.6 上的所有情况下都失败了,它抱怨派生类在 static_assert 期间不完整。
  • @RichardHodges 可能取决于您将static_assert 放在哪里。我添加了一个在 MSVC 2013 上看起来不错的完整示例。如果有机会,我会在最新的 clang 上尝试一下。
  • @DanielEarwicker 是的,当您将 static_assert 放入函数中时它可以工作。
【解决方案3】:

到目前为止,有两个很好的答案。这是另一个使用为某些方法(在本例中为构造函数)生成自定义访问键的习惯用法。它提供了正确使用的绝对保证,同时不会将基类中的私有方法暴露给派生类。

它还可以用于根据具体情况控制对基类中其他方法的访问。

template<class Derived>
struct Base
{
private:
    // make constructor private
    Base() = default;
protected:
    // This key is protected - so visible only to derived classes
    class creation_key{
        // declare as friend to the derived class
        friend Derived;
        // make constructor private - only the Derived may create a key
        creation_key() = default;
    };

    // allow derived class to construct me with a key
    Base(creation_key)
    {}

    // other methods available to the derived class go here

private:
    // the rest of this class is private, even to the derived class
    // (good encapsulation)
};

struct D1 : Base<D1>
{
    // provide the key
    D1()
    : Base<D1>(creation_key())
    {}

};

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2013-02-15
    • 1970-01-01
    • 2013-01-21
    • 2012-06-11
    • 1970-01-01
    • 2022-01-01
    • 1970-01-01
    相关资源
    最近更新 更多