派生 class 中的损坏成员变量与奇怪的重复模板模式
Corrupt member variable in derived class with Curiously Recurring Templating Pattern
我目前正在研究 CRTP,我遇到了派生 class 中的一个成员变量被破坏的问题,也就是具有垃圾值(目前有 4 个级别的多态性,顶部大多数基础 class 称为 "A",最底部的派生 class 称为 "D")。
下面是一些显示此问题示例的代码:
//A.hpp
template <class TB>
class A {
public:
A();
void CRTP_func();
};
template <class TB>
A<TB>::A() {
std::cout << "A constructor called!" << std::endl;
}
template<class TB>
void A<TB>::CRTP_func() {
std::cout << "CRTP_index called in A" << std::endl;
static_cast<TB*>(this)->CRTP_func2();
}
//B.hpp
#include "A.hpp"
#include <vector>
template<class TC>
class B : public A<B<TC>>
{
public:
B();
void CRTP_func2();
};
template<class TC>
B<TC>::B() {
std::cout << "B constructor called!" << std::endl;
}
template<class TC>
void B<TC>::CRTP_func2() {
std::cout << "CRTP_func called in B" << std::endl;
static_cast<TC*>(this)->CRTP_func3();
}
//C.hpp
#include "B.hpp"
template<class TD>
class C : B<C<TD>> {
public:
C();
void CRTP_func3();
int x;
};
template<class TD>
C<TD>::C() {
std::cout << "C constructor called" << std::endl;
}
template<class TD>
void C<TD>::CRTP_func3() {
std::cout << "CRTP_index3 called in C" << std::endl;
static_cast<TD*>(this)->CRTP_func4();
}
//D.hpp
#include "C.hpp"
class D : C<D> {
public:
D();
bool onInit();
void CRTP_func4();
C<D> top;
int y = 0;
};
D::D() {
std::cout << "D constructor called!" << std::endl;
}
bool D::onInit() {
std::cout << "D onInit called!" << std::endl;
y = 5;
return true;
}
void D::CRTP_func4() {
std::cout << y << std::endl;
std::cout << "CRTP_index4 called in D! " << std::endl;
}
//main.hpp
int main {
D * D_ptr = new D();
D_ptr->onInit();
D_ptr->top.CRTP_func3();
return 0;
}
如您所见,A 是基数 class,而 D 是派生的 class:
A<B<C<D>>>
这个程序的输出如下:
A constructor called!
B constructor called!
C constructor called
A constructor called!
B constructor called!
C constructor called
D constructor called!
D onInit called!
CRTP_index3 called in C
-33686019
CRTP_index4 called in D!
值 -33686019 在 D.hpp 中打印出值 y 并在初始化时设置为 5。经过一番挖掘后,我检查了 main.cpp 中的值,即使在进行这些 CRTP 调用后它仍设置为 5,但打印了一个垃圾值。
经过更多调试后,我意识到删除行
int x;
in B.hpp 解决了这个问题,所以我认为这个问题与一些错位有关,但我不确定为什么会发生这种情况。有谁知道为什么会发生这种情况或如何解决它?
对于冗长的 post 和模棱两可的代码,我深表歉意,为了 post.
,我尝试去除大部分复杂性并尽可能简化代码
更新:
感谢下面的评论,我找到了解决问题的方法。更好的方法是在主文件中创建一个指针,而不是使用 D::top
:
C<D> * C_ptr = static_cast<C<D>*>(D_ptr);
然后从那里调用 CRTP_func3()
:
C_ptr->CRTP_func3();
这按预期工作。
您在静态类型为 C<D>
(D::top
) 的对象上调用函数 CRTP_func3()
。函数 C<D>::CRTP_func3()
执行 static_cast<D*>(this)
但对象没有预期的类型。因此,行为未定义。
从逻辑上讲,你遇到的最基本的问题是你期望 D_Ptr
和 D_Ptr->top
对 y
具有相同的值(你说你期望 5) . D_Ptr->top
是一个完全不同的实例,即使它最终派生自 D
,也会有自己的 y
.
副本
然后,D
派生自 C
,因此 C
根本不可能派生自 D
,无论模板是否疯狂。这是您通过在 C 的 this
指针上调用 CRTP_func4
所做的假设。
此外,同一函数调用假定模板类型 TD
是 D
的一个实例。该函数调用存在于 C
中,这是 C
做出的疯狂假设——尽管我相信在这种情况下它恰好是正确的。 (那个如果不是编译器会捕捉到的)
最后关于 crtp:考虑拒绝撒旦和他的所有方式。
但说真的,显然没有完整的替身,但我想如果你充分考虑接口的力量(或 C++ 中的 pure abstract classes
),你会发现,你 可能 能够找到使用它的方法。并具有(几乎)相同的性能……当然,我不知道您的具体问题,但我强烈建议您仔细阅读这篇文章 https://en.wikipedia.org/wiki/Composition_over_inheritance
请特别查看第二个示例代码块,它是用 C# 编写的(其中 interface
将是 C++ 中的 pure abstract class
)。想想这个模式是否可以帮助你。
我目前正在研究 CRTP,我遇到了派生 class 中的一个成员变量被破坏的问题,也就是具有垃圾值(目前有 4 个级别的多态性,顶部大多数基础 class 称为 "A",最底部的派生 class 称为 "D")。
下面是一些显示此问题示例的代码:
//A.hpp
template <class TB>
class A {
public:
A();
void CRTP_func();
};
template <class TB>
A<TB>::A() {
std::cout << "A constructor called!" << std::endl;
}
template<class TB>
void A<TB>::CRTP_func() {
std::cout << "CRTP_index called in A" << std::endl;
static_cast<TB*>(this)->CRTP_func2();
}
//B.hpp
#include "A.hpp"
#include <vector>
template<class TC>
class B : public A<B<TC>>
{
public:
B();
void CRTP_func2();
};
template<class TC>
B<TC>::B() {
std::cout << "B constructor called!" << std::endl;
}
template<class TC>
void B<TC>::CRTP_func2() {
std::cout << "CRTP_func called in B" << std::endl;
static_cast<TC*>(this)->CRTP_func3();
}
//C.hpp
#include "B.hpp"
template<class TD>
class C : B<C<TD>> {
public:
C();
void CRTP_func3();
int x;
};
template<class TD>
C<TD>::C() {
std::cout << "C constructor called" << std::endl;
}
template<class TD>
void C<TD>::CRTP_func3() {
std::cout << "CRTP_index3 called in C" << std::endl;
static_cast<TD*>(this)->CRTP_func4();
}
//D.hpp
#include "C.hpp"
class D : C<D> {
public:
D();
bool onInit();
void CRTP_func4();
C<D> top;
int y = 0;
};
D::D() {
std::cout << "D constructor called!" << std::endl;
}
bool D::onInit() {
std::cout << "D onInit called!" << std::endl;
y = 5;
return true;
}
void D::CRTP_func4() {
std::cout << y << std::endl;
std::cout << "CRTP_index4 called in D! " << std::endl;
}
//main.hpp
int main {
D * D_ptr = new D();
D_ptr->onInit();
D_ptr->top.CRTP_func3();
return 0;
}
如您所见,A 是基数 class,而 D 是派生的 class:
A<B<C<D>>>
这个程序的输出如下:
A constructor called!
B constructor called!
C constructor called
A constructor called!
B constructor called!
C constructor called
D constructor called!
D onInit called!
CRTP_index3 called in C
-33686019
CRTP_index4 called in D!
值 -33686019 在 D.hpp 中打印出值 y 并在初始化时设置为 5。经过一番挖掘后,我检查了 main.cpp 中的值,即使在进行这些 CRTP 调用后它仍设置为 5,但打印了一个垃圾值。
经过更多调试后,我意识到删除行
int x;
in B.hpp 解决了这个问题,所以我认为这个问题与一些错位有关,但我不确定为什么会发生这种情况。有谁知道为什么会发生这种情况或如何解决它?
对于冗长的 post 和模棱两可的代码,我深表歉意,为了 post.
,我尝试去除大部分复杂性并尽可能简化代码更新:
感谢下面的评论,我找到了解决问题的方法。更好的方法是在主文件中创建一个指针,而不是使用 D::top
:
C<D> * C_ptr = static_cast<C<D>*>(D_ptr);
然后从那里调用 CRTP_func3()
:
C_ptr->CRTP_func3();
这按预期工作。
您在静态类型为 C<D>
(D::top
) 的对象上调用函数 CRTP_func3()
。函数 C<D>::CRTP_func3()
执行 static_cast<D*>(this)
但对象没有预期的类型。因此,行为未定义。
从逻辑上讲,你遇到的最基本的问题是你期望 D_Ptr
和 D_Ptr->top
对 y
具有相同的值(你说你期望 5) . D_Ptr->top
是一个完全不同的实例,即使它最终派生自 D
,也会有自己的 y
.
然后,D
派生自 C
,因此 C
根本不可能派生自 D
,无论模板是否疯狂。这是您通过在 C 的 this
指针上调用 CRTP_func4
所做的假设。
此外,同一函数调用假定模板类型 TD
是 D
的一个实例。该函数调用存在于 C
中,这是 C
做出的疯狂假设——尽管我相信在这种情况下它恰好是正确的。 (那个如果不是编译器会捕捉到的)
最后关于 crtp:考虑拒绝撒旦和他的所有方式。
但说真的,显然没有完整的替身,但我想如果你充分考虑接口的力量(或 C++ 中的 pure abstract classes
),你会发现,你 可能 能够找到使用它的方法。并具有(几乎)相同的性能……当然,我不知道您的具体问题,但我强烈建议您仔细阅读这篇文章 https://en.wikipedia.org/wiki/Composition_over_inheritance
请特别查看第二个示例代码块,它是用 C# 编写的(其中 interface
将是 C++ 中的 pure abstract class
)。想想这个模式是否可以帮助你。