我创建了以下测试代码以测试Curiously Recurring Template Pattern,其中我有一个Base
类的Interface()
和一个{{1 }}。它是直接根据链接的维基百科页面上最简单的示例进行建模的。
Derived
一旦编译,该程序将平稳运行并产生以下输出:
Implementation
有趣的部分是第一个测试,其中将#include <iostream>
template <class T>
class Base {
public:
void Interface() {
std::cout << "Base Interface()" << std::endl;
static_cast<T*>(this)->Implementation();
}
};
class Derived : public Base<Derived> {
public:
Derived(int data = 0) : data_(data) {}
void Implementation() {
std::cout << "Derived Implementation(), data_ = " << data_ << std::endl;
}
private:
int data_;
};
int main() {
Base<Derived> b;
b.Interface();
std::cout << std::endl;
Derived d;
d.Interface();
std::cout << std::endl;
return 0;
}
指针强制转换为Base Interface()
Derived Implementation(), data_ = -1450622976
Base Interface()
Derived Implementation(), data_ = 0
指针,并从中调用函数Base
。在此函数内部,正在访问“成员变量” Derived
。
对我们来说这是胡说八道,但是对于编译器而言,它只是该对象的内存位置偏移某个值的值。但是,在这种情况下,Implementation()
类比基类占用更多空间,因此,如果编译器认为它是从data_
对象访问数据成员,但实际上该对象是{{1} }对象,那么我们正在访问的内存可能不属于该对象,甚至不属于该程序。
看来,这种编程习惯使我们(程序员)可以非常轻松地执行非常危险的事情,例如进行看似合理的函数调用,最终导致从不受控制的内存位置进行读取。我是否正确解释了此示例的原理?如果是这样,我是否错过了CRTP范式内的技术以确保不会出现此问题?
答案 0 :(得分:0)
通常,如果您传递的指针指向在继承层次结构中某个位置包含目标类的对象,则static_cast可以正常工作。
对于
基数b;
b.Interface();
一个指向真实Base对象的指针被传递给static_cast,它根本与Derived类无关。因此,在强制转换之后,您将拥有一个类似于“派生”指针的指针,但它仍指向内存中的基础对象。通过此指针访问data_成员时,将获得内存中一些未初始化区域的内容。