目录

【C++】D指针、Q指针和二进制兼容性

D指针、Q指针和二进制兼容性的简单介绍。

二进制兼容性

考虑以下代码:

// a.h
class A
{
public:
    A();
protected:
    int a;
};

// b.h
class B : public A
{
public:
    B();
protected:
    int b;
};

若在后期代码中更改A,使得:

// a.h
class A
{
public:
    A();
protected:
    int a;
    int new_element;
};

那么编译出的动态链接库2.0将导致B的错误,进而导致程序崩溃。

原因在于编译器编译A类时,使用偏移量来标记每个元素在储存中的位置。如果A类加入的新的成员变量(成员函数不会导致这种情况),B类的偏移量将会出错,进而出现读写错误。这时,我们称这个类不能二进制兼容——改变了A,整个项目都需要重新编译。反之,如果在A类中能够添加成员变量,且不需要重新编译整个项目,那么我们称A类二进制兼容。

D指针

为了解决这个问题,我们将A类拆分为“表层”和“内层”:

// a.h
class APrivate;

class A
{
public:
    A();
protected:
    APrivate *d_ptr;
};

class APrivate
{
public:
    int a;
    int new_element;
};

注意两点:

  1. 不论如何,d_ptr不能直接在A类的声明中被调用(此时APrivate还没有被定义)。
  2. d_ptr无法被外界访问。

APrivate相当于A类的“内层”。不论APrivate内的变量如何改变,都不会影响A类这个“表层”的编译结果(A类内实质上只有一个成员变量)。此时,可以说A类已经是二进制兼容了。同时,APrivate完全不对外公开,一定程度上隐藏了实现细节。

这里指向APrivate的指针,就是D指针。

Q指针

考虑A的实现:

// a.h
class APrivate;

class A
{
public:
    A();
    int fun();
protected:
    APrivate *d_ptr;
};

class APrivate
{
public:
    int convertHelper(int b);
    int a;
    int new_element;
};

这里的APrivate中的帮助函数convertHelper需要A中的fun才能正常进行,但APrivate不能访问到A。这时,我们引入P指针,让“内层”能够访问“表层”:

// a.h
class APrivate
{
public:
    APrivate(A *parent);
    int convertHelper(int b);
    int a;
    int new_element;
    A *q_ptr;
};

此时,帮助函数就可以访问A类:

// a.h
int APrivate::convertHelper(int b)
{
    return q_ptr->fun() * 2 + 1;
}

并且在A初始化时,给APrivate自己的指针:

// a.cpp
A::A()
    : d_ptr(new APrivate(this)) {
    ...
}

继承问题

类B也想使用BPrivate,那是不是意味着每继承一层,就要多一个Private呢?不必,我们可以让BPrivate继承APrivate,并且直接用A的d_ptr承载BPrivate:

// a.h
class APrivate;

class A
{
public:
    A();
    int fun();
protected:
    A(APrivate &d);
    APrivate *d_ptr;
};

// a.cpp
A::A()
    : d_ptr(new APrivate(this)) {
    ...
}

A::A(APrivate &d)
    : d_ptr(&d) {
    ...
}

在B类中这么实现:

// b.h
class BPrivate;

class B : public A
{
public:
    B();
protected:
    B(BPrivate &d);
};

// b.cpp
B::B()
    : A(*new BPrivate(this)) {
    ...
}

B::B(BPrivate &d)
    : A(d) {
    ...
}

在BPrivate中,继承APrivate即可。

注意,B类中是没有d_ptr的。可以说,不论如何继承,都只有一个真正的Private。这时的效率其实是很高的。

在B类编写的时候,注意将d_ptr转为BPrivate:

// b.cpp
BPrivate *ptr = static_cast<BPrivate*>(d_ptr);

同时,q_ptr也需要转换:

// b.cpp
B* ptr = static_cast<B*>(q_ptr);

宏定义

如果将上述两个操作做成宏定义。在A类中添加:

// a.h
template <typename T> static inline T *GetPtrHelper(T *ptr) { return ptr; }

#define DECLARE_PRIVATE(Class) \
    inline Class##Private* d_func() { return reinterpret_cast<Class##Private*>(GetPtrHelper(d_ptr)); } \
    inline const Class##Private* d_func() const { \
     return reinterpret_cast<const Class##Private*>(GetPtrHelper(d_ptr)); \
    }\
    friend class Class##Private;

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

在APrivate中添加:

// a.h
#define 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 QPTR(Class) Class * const q = q_func()

即可一步到位设置D指针和Q指针。