// ============================================================================
//  qt_pitfall_stack_widget —— 坑点最小复现：栈对象与父子所有权打架（双重释放）
//  对应教案：OUTLINE_4DAY.md Day2 上午 ★坑卡「栈对象与父子所有权打架」
//  配套文档：docs/teaching/examples/pitfall_stack_widget/README.md
//
//  默认运行：正确写法（子控件建在堆上、交给 parent 管），离屏验证退出码 0。
//  加 --crash：错误写法（带 parent 的控件建在栈上），亲眼看双重释放崩溃——
//              课堂演示专用，退出码必然非 0，不要用于自动化验证。
// ============================================================================

#include <QApplication>
#include <QDebug>
#include <QLabel>
#include <QPushButton>
#include <QVBoxLayout>
#include <QTimer>
#include <QWidget>
#include <cstring>

// 错误写法：child 声明在 parent 之前 → 作用域结束时 parent（后声明）先析构，
// 父子树机制会 delete 它的所有子对象——包括这个根本不在堆上的 btn（UB，典型
// 表现是 glibc 报 "free(): invalid pointer" 后 abort）；随后 btn 自己的栈析构
// 又会跑一遍。一个对象只能有一个所有者：交给了 Qt 父子树，就不能再由栈管理。
static void wrongVersion() {
    QPushButton btn(QStringLiteral("我建在栈上"));   // 先声明 → 后析构
    QWidget window;                                   // 后声明 → 先析构
    btn.setParent(&window);
    // 离开作用域：window 析构 → delete &btn（栈地址！）→ 崩溃
}

// 正确写法：子控件一律 new 在堆上、把 parent 传进去（或加进布局，布局会代为
// 设置 parent），此后它的生命周期完全由父子树负责，不写任何 delete。
static QWidget* correctVersion() {
    auto* window = new QWidget;
    auto* layout = new QVBoxLayout(window);
    layout->addWidget(new QLabel(QStringLiteral("子控件都在堆上，由父子树负责释放"), window));
    layout->addWidget(new QPushButton(QStringLiteral("确定"), window));
    return window;   // window 析构时自动 delete 上面所有子对象
}

int main(int argc, char* argv[]) {
    QApplication app(argc, argv);

    if (argc > 1 && std::strcmp(argv[1], "--crash") == 0) {
        qInfo() << "演示错误写法：栈上控件交给父子树管……";
        wrongVersion();                       // 走不到下一行就崩
        qInfo() << "（如果你看到这行，说明该平台的 UB 恰好没炸——它仍然是错的）";
        return 0;
    }

    QWidget* window = correctVersion();
    window->setWindowTitle(QStringLiteral("正确写法：堆 + parent"));
    window->resize(320, 120);
    window->show();

    if (qgetenv("QT_QPA_PLATFORM") == "offscreen") {
        QTimer::singleShot(200, &app, &QApplication::quit);
    }
    int rc = app.exec();
    delete window;   // 顶层窗口没有 parent，是唯一需要我们自己负责的对象
    return rc;
}
