策略模式与CRTP静态多态之间有什么区别?

10

我想要一个可以有多种可能实现的接口,并且可以在编译时进行选择。我看到CRTP是实现这一目标的首选习惯用语。为什么会是这样?另一种替代方案是策略模式,但是我在任何地方都没有看到提及这种技术:

template <class Impl>
class StrategyInterface
{
public:
    void Interface() { impl.Implementation(); }
    void BrokenInterface() { impl.BrokenImplementation(); }

private:
    Impl impl;
};

class StrategyImplementation
{
public:
    void Implementation() {}
};

template <class Impl>
class CrtpInterface
{
public:
    void Interface() { static_cast<Impl*>(this)->Implementation(); }
    void BrokenInterface() { static_cast<Impl*>(this)->BrokenImplementation(); }
};

class CrtpImplementation : public CrtpInterface<CrtpImplementation>
{
public:
    void Implementation() {}
};

StrategyInterface<StrategyImplementation> str;
CrtpImplementation crtp;

BrokenInterface在这两种情况下都不会被编译器捕获,除非我实际尝试使用它。对我来说,策略模式似乎更好,因为它避免了丑陋的 static_cast ,并且它使用组合而不是继承。除了CRTP之外,还有其他任何CRTP允许但策略模式不允许的吗?为什么CRTP被主要使用?


在C++11中,我可能会使用C cast,例如((Impl*)this)->Implementation(),因为它允许您将CRTP类用作私有基类。 - Simple
你确定吗?我刚试了一下,它没起作用。 - Dan Nestor
这就是我的意思。根据CRTP基类的用例,您可能需要或不需要将基类中的函数公开。 - Simple
reinterpret_cast 不会应用基类到派生类的指针偏移量。C++11 有特殊措辞,使得 C 式转换执行 static_cast,但忽略可访问性。你有一个使用 static_cast 的例子吗? - Simple
1
在CRTP中,将static_cast<Impl*>(this)替换为self(),并编写Impl* self() { return static_cast<Impl*>(this); }Impl const* self() const { return static_cast<Impl*>(this); }。还要在self()方法中添加一些static_assert来断言Impl是子类。 - Yakk - Adam Nevraumont
显示剩余6条评论
2个回答

1
通常情况下,策略模式的实现与您的CRTP实现完全相同。基类定义了某种算法,放弃一些在派生类中实现的部分。
因此,CRTP实现了策略模式。您的StrategyInterface仅委托详细实现,并不是策略模式的实现。
尽管您的两个实现都可以达到相同的效果,但我更喜欢CRTP,因为它可以利用可能的空基类优化。

实际上,Strategy示例直接来自GoF书籍。不过我认为你说得对,这两个示例实际上以不同的方式实现了策略模式。 - Dan Nestor
所以如果我理解正确,唯一的区别就是我的策略示例将产生更大的对象大小? - Dan Nestor
不,OP在CRTP实现中没有错过继承。由于您似乎不理解CRTP,因此您可能不应该回答这个问题。 - Yakk - Adam Nevraumont
在我的代码中,DerivedCrtpImplementation,而 Base 则是 CrtpInterface - Dan Nestor
啊,我明白了;抱歉我错过了那个。 - Torsten Robitzki
显示剩余2条评论

0
除了静态多态性外,CRTP 还提供了覆盖基类函数的能力,因为它使用继承机制。
template <class Impl>
class BaseInterface
{
    void genericFunc() { some implementation; } 
}

如果使用CRTP,派生类可以选择将genericFunc()覆盖为“特殊”实现,以防genericFunc()不适合。策略模式将无法提供普通继承带来的功能。
策略模式的一个优点是,如果BasedInterface需要在Impl中使用依赖类型,则比CRTP要容易得多。
template <class Impl>
class BaseInterface
{
    using SomeDerivedType = typename Impl::SomeType; 
}

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接