我开始尝试CRTP,遇到了一个非常有趣的问题,这个问题让我两天都睡不着觉。
使用CRTP I声明3个结构:Column
、Header
和Footer
,它们继承自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,但没有帮助。
1条答案
按热度按时间oymdgrw71#
函数的参数
是一个
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)。