简介
d-ponter由Trolltech公司(Qt)的Arnt Gulbrandsen命名,他首次将此技术引入到Qt,使Qt成为第一个具有二进制兼容性的C++gui库。这是一个增加私有数据成员而不破坏二进制兼容性的非常棒的办法。
d-pointer技术把私有成员变量都保存在一个私有类的指针中
大致结构如下
widget.h
// Forward-declare. The definition will be in widget.cpp or in a separate file, say widget_p.h
class WidgetPrivate;
class Widget {
...
Rect geometry() const;
private:
// d-pointer never referenced in header file.
// Since WidgetPrivate is not defined in this header,
// any access will be a compile error
WidgetPrivate *d_ptr;
};
widget_p.h (_p means private)
// Forward-declare. The definition will be in widget.cpp or in a separate file, say widget_p.h
class WidgetPrivate;
class Widget {
...
Rect geometry() const;
private:
// d-pointer never referenced in header file.
// Since WidgetPrivate is not defined in this header,
// any access will be a compile error
WidgetPrivate *d_ptr;
};
struct WidgetPrivate {
Rect geometry;
};
widget.cpp
#include "widget.h"
#include "widget_p.h"
Widget::Widget()
: d_ptr(new WidgetPrivate) {// create private data
}
Rect Widget::geoemtry() const {
// the d-ptr is only accessed in the library code
return d_ptr->geometry;
}
有以下几个优点
1.二进制兼容性
变量的增减都在私有类中进行,故不改变类的大小和布局
2.减少编译时间
原因同上,类的头文件很少改变,只改变私有类,只会重新编译依赖私有类的源文件。
3.隐藏实现,头文件更简洁
优化
按照以上思想,Widget的派生类Label可以写成这样
label.h
class Label : public Widget {
...
String text();
private:
// each class maitains it's own d-pointer
LabelPrivate *d_ptr;
};
label.cpp
// Unlike WidgetPrivate, we define LabelPrivate in the source file itself
struct LabelPrivate {
String text;
};
Label::Label()
: d_ptr(new LabelPrivate) {
}
String Label::text() {
return d_ptr->text;
}
可以发现,构造一个Label会给WidgetPrivate和LabelPrivate各分配一次内存。如果继承层次更加深,那么内存分配次数将会更多。其实可以做些优化,类中加个以私有类引用为参数的构造函数,结果无论怎么继承,总共只分配一次私有类的内存。
widget.h
class Widget {
public:
Widget();
...
protected:
// only sublasses may access the below
Widget(WidgetPrivate &d); // allow subclasses to initialize with their own concrete Private
WidgetPrivate *d_ptr;
};
widget_p.h
struct WidgetPrivate {
WidgetPrivate(Widget *q) : q_ptr(q) { } // constructor that initializes the q-ptr
Widget *q_ptr; // q-ptr that points to the API class
Rect geometry;
};
widget.cpp
Widget::Widget()
: d_ptr(new WidgetPrivate(this)) {
}
Widget::Widget(WidgetPrivate &d)
: d_ptr(&d) {
}
label.h
class Label : public Widget {
public:
Label();
...
protected:
Label(LabelPrivate &d); // allow Label subclasses to pass on their Private
// notice how Label does not have a d_ptr! It just uses Widget's d_ptr.
};
label.cpp
#include "label.h"
#include "widget_p.h" // so we can access WidgetPrivate
class LabelPrivate : public WidgetPrivate {
friend class Label
String text;
};
Label::Label()
: Widget(*new LabelPrivate) // initialize the d-pointer with our own Private {
}
Label::Label(LabelPrivate &d)
: Widget(d) {
}
优化后有一点要注意,就是私有类指针转化。由于子类的私有类都是继承父类私有类的,d_ptr其实是祖先类私有类的指针,所以使用子类指针的时候要cast一下
LabelPrivate *d = static_cast<LabelPrivate *>(d_ptr);
Qt的做法
Qt3里代码于以上类似,Qt4开始使用了大量的宏,如声明私有类指针的宏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()
其中
template <typename T> static inline T *qGetPtrHelper(T *ptr) { return ptr; }
就是返回指针
参考文献: