QT的私有实现机制示例

    技术2022-05-19  27

    QT的私有实现机制

    一般来说,在C++中,几乎每一个类(class)中都需要有一些类的成员变量。

    通常情况下,我们会这样来定义成员变量:

    假如定义一个灯类,

    Class  Light{

    public:

       designA  

       material();private:   int m_color_ID;    int m_duration;  };

    我们直接把类成员变量定义在这里,有点时候甚至把这些成员变量的存取范围直接定义成是 public 的。这样做,就标准C++及面向对象编程来说,没有什么问题。

    但是,当向用户提供库的时候,会将头文件提供给用户。这样,第三方人员就会查看到你的私有数据声明以及各种函数的参数,返回值。一个库的基本脉络对有经验的程序员来说就暴露无疑了。

    最重要的是,当开发人员发布了一个库,然后将头文件提供给用户使用。这一用可能就是几年。可是某天,库的提供者发现这里面有重大的设计缺陷,必须对对库做一些改动。比如要增加一些成员变量,或者有些变量和函数需要更新,这势必会改动头文件,这样就将重新编译整个库,必然很麻烦。而且是商用软件的话,代价会更大。

    那么有没有一种方法,可以更新库而且对用户影响最小?甚至于在用户没有察觉的情况下就更新了库文件?

    QT框架就使用了一种私有实现机制。

    它把一些私有的数据以及某些更新频繁的函数放在一个专门的数据类里面,在公共类里面声明一个数据类指针指向数据类。这样就保证了修改数据成员时候,仅仅是影响到数据类,而公共类里面的数据类指针不会有任何影响,也就保证了提供给用户使用的头文件没有任何影响,不仅减少了头文件的依赖性,而且更大程度上是保证了程序的二进制兼容。

    下面,我们通过一个例子,来模仿QT是如何实现这个机制的。

    首先,我们定义一个基本的Light类和LightData类。这两个类主要是实现前面所说的公共类和数据类。

    File: Light.h

    #ifndef LIGHT_H

    #define LIGHT_H

    /*用于在公共类中,将d_ptr指针转换为对应的子数据类类型。*/

    #define CUSTOM_DECALARE_PRIVATE(Class)/

            inline Class##Private* d_func() { return   

              reinterpret_cast<Class##Private *>(d_ptr); }/

            inline const Class##Private* d_func() const/

              { return reinterpret_cast<const Class##Private* >(d_ptr); } /

            friend class Class##Private;

    /*用于在数据类中,将q_ptr指针转换为对应的子公共类类型*/

    #define CUSTOM_DECALARE_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;

     

    /*为了CPP文件调用方便,我们在定义一个宏,得到函数返回的指针,分别命名为q,d*/

    #define Q_DD(Class) Class##Private * const d = d_func()

    #define Q_QQ(Class) Class * const q = q_func()

     

    class Light;

    class LightPrivate;

    class LightData

    {

    public:

        virtual ~LightData() = 0;

        void updateValue();

        void printValue();

        Light *q_ptr;

    protected:

        int m_color_ID;

        int m_duration;

    };

     class Light

     {

        //通过下面的宏,就能在编译时,将d指针指向对应的数据类。

         CUSTOM_DECALARE_PRIVATE(Light)

     public:

         Light();

         Light(LightPrivate &dd);

          ~Light();

         virtual void printValueInHandle() = 0;

     protected:

         LightData *d_ptr;

     };

    #endif // LIGHT_H

    因为LightData是一个抽象类,只是对数据做一个浅层封装而已。因此,我们要声明一个继承至LightData的数据类来包装具体的实现。

    File:LightPrivate.h

    #ifndef LIGHTPRIVATE_H #define LIGHTPRIVATE_H #include "Light.h" class LightPrivate : public LightData {     CUSTOM_DECALARE_PUBLIC(Light) public:      ~LightPrivate();     void updateValue();     void printValue(); }; #endif // LIGHTPRIVATE_H

    这个类将是所有子数据类的根。这也解释了为什么前面的宏定义要将d_ptr转换为XXXPrivate类型的原因。我们将在这数据类中操作函数和变量。其私有实现的机制的核心就在这里。

    注意到里面定义的两个宏,这两个宏用于将任何一个类(Class)转换为对应的数据类或者公共类。这就避免了在每一个子类中定义一个数据类指针。只需要在数据类基类中定义一个d_ptr指针,通过宏来进行所需类型的转换就可以了。

    我们将CUSTOM_DECALARE_PRIVATE(Light) 这个宏拆开,你就一目了然了。

     

    inline LightPrivate* d_func() { return   

              reinterpret_cast< LightPrivate *>(d_ptr); }/

            inline const LightPrivate * d_func() const/

              { return reinterpret_cast<const LightPrivate * >(d_ptr); } /

            friend class LightPrivate;

    这样,d_ptr就正确的转换为子类(LightPrivate)的类型了。

    接下来,我们声明一个子类RedLight 继承于Light,同时,声明一个RedLight的数据类RedLightPrivate

    File: RedLight.h

    #ifndef REDLIGHT_H #define REDLIGHT_H #include "Light.h" #include <QDebug> class RedLightPrivate; class RedLight : public Light {    //转换为平行的私有数据类

     

        CUSTOM_DECALARE_PRIVATE(RedLight) public:     RedLight(); private:     void printValueInHandle(); }; #endif // REDLIGHT_H  

    File: RedLightPrivate.h

    #ifndef REDLIGHTPRIVATE_H

    #define REDLIGHTPRIVATE_H

    #include "LightPrivate.h"

    #include "RedLight.h"

    class RedLightPrivate:public LightPrivate

    {

       //转换为平行的公共类

        CUSTOM_DECALARE_PUBLIC(RedLight)

    public:

        ~RedLightPrivate();

        void updateValue();

        void printValue();

    };

    #endif // REDLIGHTPRIVATE_H

     现在,就让我们通过具体的实现文件,来看看子类是如何将自己的类型保存到基类的数据类指针。

    File: RedLight.cpp

    #include "RedLight.h"

    #include "RedLightPrivate.h"

    #include <QDebug>

    RedLight::RedLight()

        :Light(*(new RedLightPrivate))

    {

        Q_DD(RedLight);

        d->updateValue();

    }

    我们会在构造RedLight实例的时候,构造RedLight的数据类RedLightPrivate的引用

    *(new RedLightPrivate),并显示调用基类(Light)的构造函数,将该引用传递进去。

    那么基类的Light的构造函数又做了什么?我们来看看:

    File:Light.cpp

    Light::Light(LightPrivate &dd):d_ptr(&dd)

    {

      

    }

    很显然,基类Light做了同样的工作。利用初始化成员列表,用子类传进来的值继续初始化d_ptr.当这个过程完毕后,d_ptr就正确的指向了对应的子数据类!

     

    然后,我们就会在构造函数中,通过宏来获得指向当前数据类的指针,其实就是

    d_ptr,这里我们用d表示,然后呢,我们就可以放心的利用数据类的函数来更新值。

    ……

    Q_DD(RedLight);//获得d

    d->updateValue();//更新数据类的值

    ……

    接下来,还是在RedLight的公共类中,我们就可以得到数据类的值,并把它们打印出来。

    void RedLight::printValueInHandle()

    {

        //也可以直接使用该函数,而不用Q_DD

        d_func()->printValue();

     

    }

    在这里,我们即可以看到:

    在一般的设计中,我们会声明很多变量在头文件中,然后在cpp文件中定义并操纵它们,会是这样的方式:

    void RedLight::printValueInHandle()

    {

         print(m_color_ID);

         print(m_duration);

         ……

     

     

    }

    而现在,我们只需要得到一个d指针就可以了,完全杜绝了成员变量对将来的影响。

    void RedLight::printValueInHandle()

    {

        

        d_func()->printValue();

     

    }

     

    最后,当我们完成了工作,需要进行析构。d_ptr指针指向的内存必须得到释放。

    Light::~Light()

    {

        Q_DD(Light);

        delete d;

        d_ptr = 0;

    }

    这样,当我们以后有改动时候,就只需要对数据类进行修改,而公共类这边不会有任何的改动。

    这就是一个精简的QT私有实现机制示例。


    最新回复(0)