You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何为QWidget与QOpenGLWidget子类实现统一接口?

问题描述

目标

对多个绘图库做基准测试并收集诊断信息,计划给每个基准测试实现统一接口,之后在主窗口中实例化不同类。

挑战

部分基准测试基于QWidget,另一部分基于QOpenGLWidget,即便功能逻辑一致,调用时也得动态转换,操作繁琐。

已尝试方案

一开始想过用虚拟多重继承,但没法正常工作,也不确定这是不是正确方向。也考虑过组合或适配器模式,但需要在每个基准测试里重写QWidget的resizeEvent这类函数,虽然可以复制代码或者用非成员函数,但想找更优雅的实现方式。

现有代码示例

QWidgets扩展类

class BenchmarkAddin : virtual public QWidget {
public:
    BenchmarkAddin() {
        connect(&timer_, SIGNAL(timeout()), this, SLOT(update()));
    }

    double get_fps() {
        // use frames_ and time to estimate fps
    }

    void count_frame() {
        ++frames_;
    }
    
    void set_parameters(int param1_) {
        param1_;
    }

protected:
    void resizeEvent(QResizeEvent* event) override {
        init();
    }

    virtual void init() = 0;

    int param1_;
private:
    int frames_;
    QTimer timer_;
};

光栅基准测试类

class RasterBenchmark : public BenchmarkAddin {
protected:
    void init() override {
        // create buffers
    }

    void paintEvent(QPaintEvent* event) override {
        // do drawing using param1_
        count_frame();
    }
};

OpenGL基准测试类

class OpenGLBenchmark : virtual public QOpenGLWidget, public BenchmarkAddin {
protected:
    void paintGL() override {
        // do GL drawing using param1_
        count_frame();
    }
};

主窗口示例用法(无法正常运行)

BenchmarkAddin *widget; 
if (benchmark == "raster") {
    widget = new RasterBenchmark(this);
} else {
    widget = new OpenGLBenchmark(this);
}

widget->set_parameters(100);

// ...

std::cout << widget->get_fps() << std::endl;

问题在于QOpenGLWidget没有对QWidget使用虚拟继承,同时Qt对象元系统也存在兼容性问题,导致上述代码无法运行。

核心问题

如何实现一个能同时适配QWidget和QOpenGLWidget子类的统一接口?


解决方案

针对Qt中QWidget和QOpenGLWidget的继承关系限制,推荐以下两种优雅的实现方式:

方法一:纯虚接口+适配器类

定义一个不依赖Qt控件的纯虚接口,把基准测试的核心逻辑(FPS统计、参数设置等)抽离出来,然后分别为QWidget和QOpenGLWidget实现适配器类,再让具体的基准测试类继承对应的适配器。

步骤1:定义纯虚接口

class IBenchmark {
public:
    virtual ~IBenchmark() = default;
    virtual double get_fps() = 0;
    virtual void set_parameters(int param1) = 0;
    virtual void count_frame() = 0;
    virtual void init() = 0;
};

步骤2:实现QWidget基准适配器

把原BenchmarkAddin的通用逻辑移到这个适配器里,继承QWidget和IBenchmark:

class WidgetBenchmarkAdapter : public QWidget, public IBenchmark {
    Q_OBJECT
public:
    WidgetBenchmarkAdapter(QWidget* parent = nullptr) : QWidget(parent) {
        connect(&timer_, SIGNAL(timeout()), this, SLOT(update()));
        start_time_ = QDateTime::currentMSecsSinceEpoch();
    }

    double get_fps() override {
        qint64 current_time = QDateTime::currentMSecsSinceEpoch();
        double elapsed = (current_time - start_time_) / 1000.0;
        if (elapsed <= 0) return 0;
        return frames_ / elapsed;
    }

    void count_frame() override {
        ++frames_;
    }
    
    void set_parameters(int param1) override {
        param1_ = param1;
    }

protected:
    void resizeEvent(QResizeEvent* event) override {
        QWidget::resizeEvent(event);
        init();
    }

    int param1_;
private:
    int frames_ = 0;
    QTimer timer_;
    qint64 start_time_;
};

步骤3:实现OpenGL基准适配器

针对QOpenGLWidget实现适配器,同样继承QOpenGLWidget和IBenchmark:

class OpenGLBenchmarkAdapter : public QOpenGLWidget, public IBenchmark {
    Q_OBJECT
public:
    OpenGLBenchmarkAdapter(QWidget* parent = nullptr) : QOpenGLWidget(parent) {
        connect(&timer_, SIGNAL(timeout()), this, SLOT(update()));
        start_time_ = QDateTime::currentMSecsSinceEpoch();
    }

    double get_fps() override {
        qint64 current_time = QDateTime::currentMSecsSinceEpoch();
        double elapsed = (current_time - start_time_) / 1000.0;
        if (elapsed <= 0) return 0;
        return frames_ / elapsed;
    }

    void count_frame() override {
        ++frames_;
    }
    
    void set_parameters(int param1) override {
        param1_ = param1;
    }

protected:
    void resizeGL(int w, int h) override {
        QOpenGLWidget::resizeGL(w, h);
        init();
    }

    int param1_;
private:
    int frames_ = 0;
    QTimer timer_;
    qint64 start_time_;
};

步骤4:实现具体基准测试类

  • 光栅基准测试:
class RasterBenchmark : public WidgetBenchmarkAdapter {
    Q_OBJECT
protected:
    void init() override {
        // 创建缓冲区等初始化逻辑
    }

    void paintEvent(QPaintEvent* event) override {
        // 使用param1_进行光栅绘图
        count_frame();
    }
};
  • OpenGL基准测试:
class OpenGLBenchmark : public OpenGLBenchmarkAdapter {
    Q_OBJECT
protected:
    void init() override {
        // OpenGL上下文初始化、着色器加载等逻辑
    }

    void paintGL() override {
        // 使用param1_进行OpenGL绘图
        count_frame();
    }
};

主窗口调用

现在可以通过IBenchmark接口统一操作,同时如果需要把控件添加到窗口,只需动态转换为QWidget(因为QOpenGLWidget本身也是QWidget的子类):

IBenchmark* benchmark;
QWidget* widget;

if (benchmark_type == "raster") {
    auto* raster = new RasterBenchmark(this);
    benchmark = raster;
    widget = raster;
} else {
    auto* gl = new OpenGLBenchmark(this);
    benchmark = gl;
    widget = gl;
}

benchmark->set_parameters(100);
widget->show(); // 添加到窗口布局或直接显示

// ...

std::cout << benchmark->get_fps() << std::endl;

方法二:组合模式(基准逻辑与控件分离)

把基准测试的核心逻辑(FPS统计、参数管理)封装成一个独立的类,然后在QWidget和QOpenGLWidget的子类中组合这个类,通过转发调用实现统一接口。

步骤1:封装基准核心逻辑

class BenchmarkCore {
public:
    BenchmarkCore() {
        start_time_ = QDateTime::currentMSecsSinceEpoch();
    }

    double get_fps() {
        qint64 current_time = QDateTime::currentMSecsSinceEpoch();
        double elapsed = (current_time - start_time_) / 1000.0;
        if (elapsed <= 0) return 0;
        return frames_ / elapsed;
    }

    void count_frame() {
        ++frames_;
    }
    
    void set_parameters(int param1) {
        param1_ = param1;
    }

    int get_param1() const { return param1_; }

private:
    int frames_ = 0;
    int param1_ = 0;
    qint64 start_time_;
};

步骤2:定义统一接口(可选,用于约束)

class IBenchmarkWidget {
public:
    virtual ~IBenchmarkWidget() = default;
    virtual double get_fps() = 0;
    virtual void set_parameters(int param1) = 0;
    virtual QWidget* get_widget() = 0;
};

步骤3:实现具体基准测试类

  • 光栅基准测试:
class RasterBenchmark : public QWidget, public IBenchmarkWidget {
    Q_OBJECT
public:
    RasterBenchmark(QWidget* parent = nullptr) : QWidget(parent) {
        connect(&timer_, SIGNAL(timeout()), this, SLOT(update()));
    }

    double get_fps() override {
        return core_.get_fps();
    }

    void set_parameters(int param1) override {
        core_.set_parameters(param1);
    }

    QWidget* get_widget() override {
        return this;
    }

protected:
    void resizeEvent(QResizeEvent* event) override {
        QWidget::resizeEvent(event);
        init();
    }

    void paintEvent(QPaintEvent* event) override {
        // 使用core_.get_param1()进行光栅绘图
        core_.count_frame();
    }

private:
    void init() {
        // 创建缓冲区等初始化逻辑
    }

    BenchmarkCore core_;
    QTimer timer_;
};
  • OpenGL基准测试:
class OpenGLBenchmark : public QOpenGLWidget, public IBenchmarkWidget {
    Q_OBJECT
public:
    OpenGLBenchmark(QWidget* parent = nullptr) : QOpenGLWidget(parent) {
        connect(&timer_, SIGNAL(timeout()), this, SLOT(update()));
    }

    double get_fps() override {
        return core_.get_fps();
    }

    void set_parameters(int param1) override {
        core_.set_parameters(param1);
    }

    QWidget* get_widget() override {
        return this;
    }

protected:
    void resizeGL(int w, int h) override {
        QOpenGLWidget::resizeGL(w, h);
        init();
    }

    void paintGL() override {
        // 使用core_.get_param1()进行OpenGL绘图
        core_.count_frame();
    }

private:
    void init() {
        // OpenGL上下文初始化逻辑
    }

    BenchmarkCore core_;
    QTimer timer_;
};

主窗口调用

IBenchmarkWidget* benchmark;

if (benchmark_type == "raster") {
    benchmark = new RasterBenchmark(this);
} else {
    benchmark = new OpenGLBenchmark(this);
}

benchmark->set_parameters(100);
benchmark->get_widget()->show(); // 添加到窗口

// ...

std::cout << benchmark->get_fps() << std::endl;

方案对比

  • 方法一:通过适配器类复用通用控件逻辑,代码复用率高,适合需要重写多个控件事件(如resizeEvent、paintEvent)的场景,接口清晰。
  • 方法二:核心逻辑与控件完全解耦,更灵活,适合后续扩展其他非Qt控件的基准测试,维护成本低。

内容的提问来源于stack exchange,提问作者Chris

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.24 18:54:29