c++ 为什么在使用CRTP时,结构字段会查看未初始化的内存?

enyaitl3  于 2023-05-08  发布在  其他
关注(0)|答案(1)|浏览(120)

我开始尝试CRTP,遇到了一个非常有趣的问题,这个问题让我两天都睡不着觉。
使用CRTP I声明3个结构:ColumnHeaderFooter,它们继承自Widget
Column结构接受构造函数中的其他小部件,并将它们存储在std::tuple中,然后在Render()方法中调用存储在std::tuple中的小部件的Render()方法。
main()中,您可以看到Header小部件的title字段被设置了,但是在调试Column小部件的Render()方法的过程中,我注意到title字段查看未初始化的内存,所以程序以segfault结束。
代码使用标准c++20
谁能说说问题出在哪里?先谢谢你了!

#include <iostream>
#include <tuple>
#include <string>

template<typename T>
struct Widget {
    void Render() {
        static_cast<T*>(this)->Render();
    }
};

template<typename ...Args>
struct Column : public Widget<Column<Args...>> {
    Column(Widget<Args>... args)
        : children(std::forward<Widget<Args>>(args)...)
    {}

    void Render() {
        std::cout << "Column {" << std::endl;

        std::apply([](auto&... widgets){
            auto render = [](auto& widget){
                widget.Render();
            };

            (render(widgets), ...);
        }, children);

        std::cout << "}" << std::endl;
    }

private:
    std::tuple<Widget<Args>...> children;
};

struct Header : public Widget<Header> {
    void Render() {
        std::cout << "Header { title=" << this->title << " }" << std::endl;
    }

    std::string title;
};

struct Footer : public Widget<Footer> {
    void Render() {
        std::cout << "Footer {}" << std::endl;
    }
};

template<typename T>
void AplicationRun(Widget<T> widget) {
    widget.Render();
}

int main() {
    auto app = Column{
        Header{
            .title = "Title"
        },
        Column{},
        Footer{},
    };

    AplicationRun(app);

    return 0;
}

我尝试在Column构造函数中使用move sematic,但没有帮助。

oymdgrw7

oymdgrw71#

函数的参数

template<typename T>
void AplicationRun(Widget<T> widget) {

是一个Widget<Column>,它不是一个Column。与您可能习惯的其他语言不同,此参数不是对对象的引用,而实际上是整个对象。为了使Column对象适合这个Widget<Column>参数,编译器只复制Widget<Column>部分,这是一个称为slicing的常见陷阱,因为参数只是原始对象的“切片”。
您可以通过将Widget<T>更改为T来解决此问题。但是,它仍然会复制Column对象,我想你不希望这样。
为了避免复制,可以通过引用传递:Widget<T> &T &。任何一个都可以,因为Widget<T> &参数 * 可以 * 是对Column对象的引用。
如果通过引用传递,您还可以选择使用Widget<T> const &T const &,以防止函数修改对象(并且Render函数需要标记为const,以便您仍然可以调用它)。在这个小程序中,如果你不想的话,你不需要这样做,但是对于大型程序的程序员来说,这样限制自己是很常见的,因为它有助于避免错误(bug)。

相关问题