我今天一直在学习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
在基类型方面会更好吗?如果没有,为什么?
答案 0 :(得分:8)
简短回答
这取决于你需要做什么,但如果不同的派生类提供不同类型的x, y
,那么这些不能在基类中。
答案很长
继承的最常见用法是基类包括许多(多个)派生类中常见的内容。这个通用部分只编写一次并由每个派生类重用,从而使代码更短,更简洁,更易于维护等。在您的情况下,常见部分为calculate()
。
现在,无论此公共代码需要访问每个派生类专用的信息,都需要通过公共接口访问此信息。在您的示例中,此信息是成员x, y
,每个派生类可能具有不同的类型。或者,它可以是成员函数x(), y()
。这些函数可以采用不同类型(但数量相同)的参数,并且每个派生类具有不同的返回类型。
无论哪种方式,派生类的工作都是为异构信息提供通用接口。对于CRTP /静态多态,在成员函数的情况下,此公共接口仅仅是成员的名称和参数的数量。对于动态多态,相关机制是虚函数,公共接口包括函数的整个签名。
实际存储数据的位置无关紧要;这取决于很多事情。可能的情况是,数据毕竟存储在基类中,但仍然可以通过派生类中的成员函数访问它们。
一个示例是一个元组实现,其中base class实现了不同类型元组之间的所有常见功能,而一些tuple views是从此基础派生的,以模拟像flipping这样的操作元组元素的顺序,concatenating或"zipping"元组在一起等等。请注意,所有这些视图都是惰性的,类似于std::reverse_iterator
允许您以相反的顺序遍历序列而不实际操作的方式数据提前。
在这种情况下,基类的成员函数at()
提供对元组元素的随机访问。这将调用派生类的call_at()
,派生类又访问实际存储在基类中的数据。因此,每个派生类只知道每个元素的位置;使用此信息,基类实现所有剩余的功能(例如,产生新元组的operator[]
,其中每个元素是将operator[]
应用于原始元组的相应元素的结果。
D'template mixins为CRTP提供了一种更方便,更简洁的替代方案;几乎和宏一样方便。在这种情况下,您的static_cast<DerivedType&>(*this).x
和我的der().x
只是x
。另外,您根本不需要DerivedType
内的Base
。
答案 1 :(得分:1)
我认为最好假设派生类有两个成员函数x()
和y()
。您可以更改Base::calculate()
的实现以使用这些函数,而不是使用成员变量。
然后,派生类在它拥有的数据类型中有更多的自由。