2017-08-24 15 views
5

我很担心在Qt 5.9下开发的小部件库可能在将来升级,而无需重新编译已经使用它的代码。当然,我已经开始使用这个成语,并且其中描述的Qt版本为herehere。然而,当我试图调整我的代码时,我想出了一个想法,即不是添加新的数据成员,而是将它们移动到一个单独的私有类,我可以使用Qt的带有lambda函数的信号/插槽机制,并且只有局部变量。让我们来具体说明这个思想与下面的例子:是否使用C++ Lambda函数作为Qt中的槽帮助保存库的二进制兼容性?

方案A:

class Foo : public QWidget 
{ 
    Q_OBJECT 
public: 
    explicit Foo(QWidget *parent = nullptr); 

private: 
    // A bunch of data members 
    QPushButton *m_button; 
    QLineEdit *m_lineEdit; 
    QCheckBox *m_checkBox; 
    QString m_str; 

private slots: 
    void on_pushButtonClicked(); 
    void on_checkBoxStateChanged(int state); 
}; 

Foo::Foo(QWidget *parent) : 
    QWidget(parent), 
    m_button(new QPushButton("Click me", this)); 
    m_lineEdit(new QLineEdit(this)), 
    m_checkBox(new QCheckBox(this)), 
    m_str("Initial text") 
{ 
    connect(button, &QPushButton::clicked, this, &Foo::on_pushButtonClicked); 
    connect(checkBox, &QCheckBox::stateChanged, this, &Foo::on_checkBoxStateChanged); 
} 

Foo::on_pushButtonClicked() 
{ 
    m_str = m_lineEdit->text(); 
    m_lineEdit->setDisabled(m_checkBox->isChecked()); 
} 

Foo::on_checkBoxStateChanged(int state) 
{ 
    m_button->setText(state == Qt::Checked ? m_str : "Click me") 
} 

方案B:

class Foo : public QWidget 
{ 
    Q_OBJECT 
public: 
    explicit Foo(QWidget *parent = nullptr); 
}; 

Foo::Foo(QWidget *parent) : QWidget(parent) 
{ 
    QPushButton *button = new QPushButton("Click me", this); 
    QLineEdit *lineEdit = new QLineEdit(this); 
    QCheckBox *checkBox = new QCheckBox(this); 
    QString str("Initial text"); 

    connect(button, &QPushButton::clicked, [=](){ 
     str = lineEdit->text(); 
     lineEdit->setDisabled(checkBox->isChecked()); 
    }); 

    connect(checkBox, &QCheckBox::stateChanged, [=](int state){ 
     button->setText(state == Qt::Checked ? str : "Click me") 
    }); 
} 

所以,变型B - 除了是更紧凑它不包含任何类数据成员,所以没有隐藏变量,因此不需要D指针。二进制兼容性仍然保证虽然(或是否?),如果将来构造函数重新实现了额外的本地变量使用相同的信号/插槽方式。我是否认为这样做会行得通,或者这种方法根本无法解决问题?

注意:有关在Qt中使用lambdas作为插槽的更多信息,请查看@Igor Tandetnik的评论here

+0

我不认为这会编译。 lambda中'str'将是const。即使它编译完成,请注意,按值捕获:在这两个不同的lambda中,“str”指的是不同的独立对象。你必须用堆分配的指针来保存所有的东西 - 一个类固醇上的'pimpl'。 –

+0

@IgorTandetnik,我有你的观点。然而,这个构造函数的唯一重新实现不应该破坏二进制兼容性,应该如何呢? – scopchanov

+0

我不太清楚“二进制兼容性”在这里的含义。只要您不修改定义了'Foo'类的头文件,您就不需要使用'Foo'重新编译源文件,如果这就是您要求的。也就是说,你会很难用Variant B的风格编写任何重要的代码。例如,在一个普通的类中,你可以有一个私人成员函数,你可以从多个地方调用。你打算如何重用代码?在构造函数中为每个“成员函数”创建一个lambda,并让所有连接处理函数捕获它?它会很快变得非常尴尬。 –

回答

2

我想出了一个主意,那不是增加新的数据成员,并将其移动到一个单独的专用类[...]

这是错误的方式去思考它。该界面没有数据成员。无论你有什么会员,直接进入PIMPL。你不“移动”任何东西,你不要将它们加入错误的地方开始。

此外,具有与父对象相同生命周期的成员的堆分配是过早的悲观化。将它们按价值存储在PIMPL中。

[...]我可以使用Qt的信号/槽机制lambda函数和只有局部变量

这不会只要你需要工作来存储的东西比QObject以上儿童不滥用财产制度。

这不是一个灵活的方法,它确实不难做到。 Qt建立了所有必要的模式。有关详细信息,请参阅this question

你不打算从派生的类不需要单独的Class_p.h标题。您可以将ClassPrivate定义添加到Class.cpp文件本身的开头。

// Foo.h 
#include <QWidget> 
class FooPrivate; 
class Foo : public QWidget { 
    Q_OBJECT 
    Q_DECLARE_PRIVATE(Foo) 
    QScopedPointer<FooPrivate> const d_ptr; 
public: 
    explicit Foo(QWidget *parent = {}); 
    ~Foo(); 
protected: 
    Foo(FooPrivate &, QWidget *parent = {}); // for expansion 
}; 
// Bar.h 
#include "Foo.h" 
class BarPrivate; 
class Bar : public Foo { 
    Q_OBJECT 
    Q_DECLARE_PRIVATE(Bar) 
    Q_PROPERTY(int data READ data) 
public: 
    explicit Bar(QWidget *parent = {}); 
    ~Bar(); 
    int data() const; 
protected: 
    Bar(BarPrivate &, QWidget *parent = {}); // for expansion 
}; 
// Foo_p.h 
#include "Foo.h" 

class FooPrivate { 
    Q_DECLARE_PUBLIC(Foo) 
    Q_DISABLE_COPY(Foo) // usually desired 
    Foo * const q_ptr; 
public: 
    QVBoxLayout m_layout{q_ptr}; 
    QPushButton m_button{q_ptr->tr("Hello!")}; 
    QLineEdit m_lineEdit; 
    QCheckBox m_checkBox{q_ptr->tr("Active")}; 

    void on_pushButtonClicked(); 
    void on_checkBoxStateChanged(int state); 

    explicit FooPrivate(Foo *); 
    virtual ~FooPrivate() {} // we're meant to be derived from! 
}; 
// Bar_p.h 
#include "Foo_p.h" 
#include "Bar.h" 

class BarPrivate : public FooPrivate { 
    Q_DECLARE_PUBLIC(Bar) 
public: 
    int m_data = 44; 

    explicit BarPrivate(Bar *); 
}; 
// Foo.cpp 
#include "Foo_p.h"  

Foo::Foo(QWidget * parent) : 
    Foo(*new FooPrivate(this), parent) 
{} 

Foo::Foo(FooPrivate & d_ptr, QWidget * parent) : 
    QWidget(parent), 
    d_ptr(d_ptr) 
{} 

Foo::~Foo() {} 

FooPrivate::FooPrivate(Foo * q_ptr) : 
    q_ptr(q_ptr) 
{ 
    m_layout.addWidget(&m_button); 
    m_layout.addWidget(&m_lineEdit); 
    m_layout.addWidget(&m_checkBox); 
    connect(&m_button, &QPushButton::clicked, [=]{ on_pushButtonClicked(); }); 
    connect(&m_checkBox, &QCheckBox::stateChanged, [=](int s){ on_checkBoxStateChanged(s); }); 
} 
// Bar.cpp 
#include "Bar_p.h" 

Bar::Bar(QWidget * parnet) : 
    Bar(*new BarPrivate(this), parent) 
{} 

Bar::Bar(BarPrivate & d_ptr, QWidget * parent) : 
    Foo(d_ptr, parent) 
{} 

Bar::~Bar() {} 

BarPrivate::BarPrivate(Bar * q_ptr) : 
    FooPrivate(q_ptr) 
{} 

int Bar::data() const { 
    Q_D(const Bar); 
    return d->m_data; 
} 
+0

首先,感谢您的解释和提供的好例子!我必须补充说,我已经检查过链接,并且在这个特殊的问题中,它没有介绍如何在Qt中使用PIMPL,但是可以用其他方法替代它(例如广泛使用lambda表达式)。据我了解,你的意见是 - 不是,它不能被替代。至于“移动”,我的意思是重新设计我的图书馆,目前没有使用习语,因此应该以类似的方式重写。尽管如此,我还是遇到了一些具体的问题,我认为这值得开一个新的问题。 – scopchanov

+0

@scopchanov是的,请打开另一个问题。看看你还碰到了什么会很有趣。 –

+0

我会在今晚做,并提及你的名字让你知道。我渴望听到你的意见。 – scopchanov