我今天一直在学习 CRTP(奇怪的重复模板模式),相信我已经足够了解它了。
但是,在我看到的示例中,状态存储在派生类型中,即使基类型依赖于它们的存在。对我来说,这似乎不合逻辑,成员变量应该在基类型中,因为它的功能依赖于它们。
下面是我所说内容的一个简单示例:
template <typename DerivedType>
class Base{
public:
int calculate() {
return static_cast<DerivedType&>(*this).x + static_cast<DerivedType&>(*this).y;
}
};
class Derived : Base<Derived>{
public:
int x; // ignore the fact that these aren't initialised for simplicity
int y;
};
我的问题是:
我认为成员 x
和 y
在基类型中会更好吗?如果不是,为什么?
最佳答案
简答
这取决于你需要做什么,但是如果不同的派生类提供不同类型的x, y
, 那么这些不能在基类中。
长答案
继承的最常见用途是基类包含许多(多个)派生类中共有的任何内容。这个公共(public)部分只写一次并被每个派生类重用,导致更短、更清晰的代码、更容易维护等。公共(public)部分是 calculate()
在你的情况下。
现在,只要此公共(public)代码需要访问每个派生类的专用信息,就需要通过公共(public)接口(interface)访问此信息。在您的示例中,此信息是成员 x, y
每个派生类的类型可能不同。或者,它可以是成员函数 x(), y()
.这些函数可以采用不同类型(但数量相同)的参数,并且每个派生类具有不同的返回类型。
无论哪种方式,派生类的工作都是为异构信息提供通用接口(interface)。对于 CRTP/静态多态性,在成员函数的情况下,这个公共(public)接口(interface)仅仅是成员的名称和参数的数量。对于动态多态,相关机制是虚函数,公共(public)接口(interface)包括函数的整个签名。
数据实际存储在哪里并不重要;这取决于很多事情。很可能数据毕竟存储在基类中,但是它们仍然可以通过派生类中的成员函数访问。
一个例子是元组实现,其中 base class实现了不同类型元组之间的所有通用功能,而许多 tuple views从这个基础派生到模型操作,如 flipping元组元素的顺序,concatenating或 "zipping"元组等。请注意,所有此类 View 都是惰性 View ,类似于 std::reverse_iterator
的方式。允许您以相反的顺序遍历序列,而无需提前实际操作数据。
在这种情况下,成员函数at()
基类提供对元组元素的随机访问。这称为 call_at()
派生类访问实际存储在基类中的数据。所以,每个派生类只知道在哪里可以找到每个元素;使用此信息,基类实现所有剩余的功能(例如,一个 operator[]
产生一个新的元组,其中每个元素都是将 operator[]
应用于原始元组的相应元素的结果)。
D 的 template mixins提供比 CRTP 更方便、更简洁的替代方案;几乎和宏一样方便。你的static_cast<DerivedType&>(*this).x
和我的 der().x
将只是 x
在这种情况下。另外,您不需要 DerivedType
在 Base
内完全没有。
关于c++ - CRTP 基类型使用的成员变量应该在派生类型中吗?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/25048534/