【问题标题】:Corrupt member variable in derived class with Curiously Recurring Templating Pattern具有奇怪重复模板模式的派生类中的损坏成员变量
【发布时间】:2018-11-06 01:02:18
【问题描述】:

我目前正在使用 CRTP,并且遇到了派生类中的成员变量被损坏的问题,即具有垃圾值(目前有 4 级多态性,最顶层的基类称为“A " 和最底层的派生类 "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 是基类,而 D 是派生类:

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 解决了这个问题,所以我认为这个问题与一些错位有关,但我不确定为什么会发生这种情况。有谁知道为什么会发生这种情况或如何解决?

抱歉,帖子太长,代码模棱两可,为了帖子,我尽量去除大部分复杂性并尽可能简化代码。

更新:

感谢下面的 cmets,我找到了解决问题的方法。除了使用D::top,更好的方法是在主文件中创建一个指针,如下所示:

C&lt;D&gt; * C_ptr = static_cast&lt;C&lt;D&gt;*&gt;(D_ptr);

然后从那里调用CRTP_func3()

C_ptr-&gt;CRTP_func3();

这按预期工作。

【问题讨论】:

    标签: c++ polymorphism crtp


    【解决方案1】:

    您在静态类型为C&lt;D&gt; (D::top) 的对象上调用函数CRTP_func3()。函数C&lt;D&gt;::CRTP_func3() 执行static_cast&lt;D*&gt;(this) 但对象没有预期的类型。因此,行为是未定义的。

    【讨论】:

    • 感谢您的回答!我对 CRTP 工作原理的理解可能存在缺陷,但我认为对象的预期类型是 D,因此 'static_cast&lt;D*&gt;(this) 对我来说很有意义。既然不是这样,那么对象的预期类型是什么?
    • @lockdown: 成员声明为C&lt;D&gt; top; 为什么该对象会将类型更改为D?仅仅因为你还有类D,它派生自C&lt;D&gt;,所以所有C&lt;D&gt;对象的类型都不会改变。
    • 谢谢!我现在才意识到我哪里错了。更好的方法是使用C&lt;D&gt; * C_ptr = static_cast&lt;C&lt;D&gt;*&gt;(D_ptr); 而不是D::top
    【解决方案2】:

    从逻辑上讲,您遇到的最基本问题是您期望 D_PtrD_Ptr-&gt;top 具有相同的 y 值(您说您期望 5)。 D_Ptr-&gt;top 是一个完全不同的实例,即使它最终派生自 D,也会有自己的 y 副本。

    然后,D 派生自 C,因此无论模板是否疯狂,C 从根本上不可能派生自 D。这是您通过在 C 的 this 指针上调用 CRTP_func4 所做的假设。

    此外,相同的函数调用假定模板类型TDD 的一个实例。该函数调用存在于C 中,这是C 做出的疯狂假设——尽管我相信在这种情况下它恰好是正确的。 (那个如果不是编译器会捕获的)

    最后关于 crtp:考虑拒绝撒旦和他所有的方式。

    但说真的,显然没有完全的替代品,但我想你会发现,如果你充分考虑接口的力量(或 C++ 中的 pure abstract classes),你也许能够找到使用它的方法。并且具有(几乎)相同的性能...当然,我不知道您的具体问题,但我强烈建议您仔细阅读这篇文章https://en.wikipedia.org/wiki/Composition_over_inheritance

    特别注意第二个示例代码块,它是用 C# 编写的(其中 interface 在 C++ 中是 pure abstract class)。考虑一下这种模式是否可以帮助您。

    【讨论】:

    • 你是对的!我通过创建C&lt;D&gt; 的新实例来创建D 的新实例,因此y 的值显然会有所不同。非常感谢你的帮助!至于 CRTP,我正在使用它,因为它比虚拟功能快得多,而且速度对我的项目至关重要。
    • @lockdown - 编辑了我的答案。我希望它会有所帮助。
    猜你喜欢
    • 2021-07-25
    • 2023-01-09
    • 1970-01-01
    • 1970-01-01
    • 2017-02-19
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多