Qt的D与Q指针

库的二进制兼容

对于一个已经发布的库,如果在库的某个接口类中增加了一个成员,并重新发布该库,如果使用该库的程序直接替换该库后运行时会导致程序崩溃,解决办法就是重新编译应用程序,这是因为,新库中某些接口类及继承于它的类内存布局都已变化,但是旧的应用程序是根据之前的类内存布局,在编译期间分配好了内存,如果不重新编译程序,那么就会导致访问一个数据时,其可能已被覆盖,从而导致崩溃。
所以二进制兼容是指如果自己的程序使用了第三方模块,二进制兼容可以保证在第三方模块修改之后,自己的程序可以不用重新编译就能够兼容修改后的第三方模块。

实现方法

它其实就是pimpl设计模式的具体应用。在了解二进制兼容实现方法前,引入两个概念:主类和私有类。我把二进制库提供给第三方程序的接口类称为主类,把二进制程序内部使用的类称作私有类。只要不修改主类的成员,无论怎么修改二进制库,都可以做到二进制兼容。为直观说明二进制兼容的优势,举个例子,假如我们写了一个库,提供了如下的接口:

class Interface{
public:
    Interface(){m_greeting="hello\n";}
    void say(){
        std::cout<<m_greeting;
    }
private:
    std::string m_greeting;
}

接下来我们因为新的需求,需要对库进行修改,修改如下:

class Interface{
public:
    Interface(){m_greeting="hello\n";m_name="mike";}
    void say(){
        std::cout<<m_greeting+m_name;
    }
private:
    std::string m_greeting;
    std::string m_name;
}

这时候,由于主类成员发生了修改,我们不仅要重新编译库,还要重新编译所有基于这个库的程序。

接下来我们对上述代码进行重构,修改成二进制兼容模式,二进制兼容的实现方式类似于设计模式中的Pimpl,关键点就是在主类中定义一个指向私有类的指针,简单实现方式如下:

//导出类 interface.h
class Interface{
public:
    Interface(){m_ptr = new ClassPrivate;}
    ~Interface(){delete m_ptr;}
    void say(){
        if(m_ptr!=nullptr){m_ptr->say();}
    }
private:
    ClassPrivate* m_ptr;
}
//私有类 classprivate.h
class ClassPrivate{
public:
    ClassPrivate(){m_greeting="hello\n";}
    void say(){std::cout<<m_greeting;}
private:
    std::string m_greeting;
}

当需求发生变动,我们只需要作出如下修改:

class ClassPrivate{
public:
    ClassPrivate(){m_greeting="hello\n";m_name="mike";}
    void say(){std::cout<<m_greeting+m_name;}
private:
    std::string m_greeting;
    std::string m_name;
}

因为我们没有修改主类Interface,只是对私有类进行了修改,因此不需要重新编译基于该库的程序,缩短了编译流程。

好处

私有的结构体可以随意改变,而不需要重新编译整个工程项目
隐藏实现细节
头文件中没有任何实现细节,可以作为API使用
原本在头文件的实现部分转移到乐源文件,所以编译速度有所提高

Qt中Q和D指针实现

  • d指针是在主类中使用的,主类获取私有类或类中私有变量的指针
  • q指针是在私有数据类中使用的,来获取主类对象指针
    Q_DECLARE_PRIVATE宏和Q_D宏配合用来在主类中访问私有类对象
#define Q_DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() { return reinterpret_cast<Class##Private *>(qGetPtrHelper(d_ptr)); } \
    inline const Class##Private* d_func() const { return reinterpret_cast<const Class##Private *>(qGetPtrHelper(d_ptr)); } \
    friend class Class##Private;
#define Q_D(Class) Class##Private * const d = d_func()

Q_DECLARE_PUBLIC宏和Q_Q宏配合用来在私有类中访问主类对象

Q_DECLARE_PUBLIC宏和Q_Q宏
#define Q_DECLARE_PUBLIC(Class)                           \
    inline Class* q_func() { return static_cast<Class *>(q_ptr); } \
    inline const Class* q_func() const { return static_cast<const class*>(q_ptr); } \
    friend class Class;
#define Q_Q(Class) Class * const q = q_func()

如下为一个完整例子:

// myclass.h
#ifndef MYCLASS_H
#define MYCLASS_H
#include <QtCore/QObject>
class MyClassPrivate;
class MyClass: public QObject
{
  Q_OBJECT
public:
  MyClass(QObject *parent = 0);
  virtual ~MyClass();
  void dummyFunc();
private:
  MyClassPrivate * const d_ptr;
  Q_DECLARE_PRIVATE(MyClass);
};
#endif // MYCLASS_H
// myclass.cpp
#include "myclass.h"
class MyClassPrivate
{
public:
  MyClassPrivate(MyClass *parent)
    : q_ptr(parent)
  {
  }
  void foobar()
  {
    Q_Q(MyClass);
    emit q->dummySignal();
  }
private:
  MyClass * const q_ptr;
  Q_DECLARE_PUBLIC(MyClass);
};
MyClass::MyClass(QObject *parent)
  : QObject(parent)
  , d_ptr(new MyClassPrivate(this))
{
}
MyClass::~MyClass()
{
  Q_D(MyClass);
  delete d;
}
void MyClass::dummyFunc()
{
  Q_D(MyClass);
  d->foobar();
}
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

推荐阅读更多精彩内容

  • 本文翻译自 Policies/Binary Compatibility Issues With C++ 二进制兼容...
    赵者也阅读 2,317评论 0 3
  • QT的D指针 相信不少刚开始阅读Qt源代码的朋友在看到其中的Private类和诸如Q_D、Q_Q等宏时都会思考,为...
    yangpaul98阅读 1,455评论 0 1
  • D-Pointer简介 如果你经常阅读QT的源码,你会看到大量的Q_D和Q_Q宏.这篇文章将会揭开这些宏的用处.这...
    托尼章阅读 1,030评论 1 1
  • Qt/QML 编码规范 一. 概述 良好的编码规范可以大幅提高程序的可读和可理解性,最终目标是实现更友好的可维护性...
    赵者也阅读 2,257评论 0 5
  • 本文翻译自: https://wiki.qt.io/Coding_Conventions原作者: Qt原文发布时间...
    Qt君阅读 380评论 0 0