我创建了以下测试代码来试验 Curiously Recurring Template Pattern ,其中我有一个带有 Interface()
的 Base
类和一个带有 Implementation
的 Derived
类。它直接根据链接的维基百科页面中最简单的示例建模。
#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_
。
对我们来说这是无稽之谈,但对编译器来说它只是该对象内存位置的某个偏移量的值。但是,在这种情况下,Derived
类比基类占用更多空间,因此如果编译器认为它正在从 Derived
对象访问数据成员,但实际上是对象是一个Base
对象,那么我们正在访问的内存可能不属于这个对象,甚至不属于这个程序。
似乎这种编程习惯让我们(程序员)很容易做出非常危险的事情,比如进行看似合理的函数调用,但最终却从不受控制的内存位置读取数据。我是否正确解释了这个例子的机制?如果是这样,我是否错过了 CRTP 范例中的一些技术来确保不会出现此问题?
最佳答案
一般情况下,如果您传递的指针指向一个对象,该对象在其继承层次结构中某处包含目标类,则 static_cast 可以正常工作。
为了
基地 b;
b.接口(interface)();
一个指向真实Base对象的指针被传递给static_cast,它与Derived类完全无关。所以在转换之后你有一个指针看起来像一个指向 Derived 的指针,但它仍然指向内存中的一个 Base 对象。当您通过该指针访问 data_ 成员时,您将获得内存中某个未初始化区域的内容。
关于c++ - CRTP - 危险的内存访问?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52599394/