Pointer-Implementation

Auf die Frage wie man in C++ Implementierungsdetails versteckt und darüber hinaus auch die Erweiterbarkeit von Klassen gewährleistet gibt es eine Antwort: Pointer-Implementierung, kurz Pimpl oder in Qt auch D-Pointer genannt. In diesem Beitrag zeige ich wie in Qt die Pointer-Implementierung realisiert wird.

Im folgenden Beispiel wird eine Formularklasse defniert. Ein Formular verfügt über einen Formularkopf, einen Formularfuß und natürlich den Formularkörper. Die Header-Datei dazu

#ifndef FORM_H
#define FORM_H
 
#include <QWidget>
 
class FormPrivate;
 
class Form : public QWidget
{
    Q_OBJECT
    Q_DISABLE_COPY(Form)
    Q_DECLARE_PRIVATE(Form)
 
public:
    explicit Form(QWidget *parent = 0);
    virtual ~Form();
 
    QWidget *header() const;
    void setHeader(QWidget *header);
 
    QWidget *body() const;
    void setBody(QWidget *body);
 
    QWidget *footer() const;
    void setFooter(QWidget *footer);
 
private:
    FormPrivate *d_ptr;
};
 
#endif // FORM_H

Obligatorisch bei der Erweiterung von QObject, hier in Form vom QWidget, ist das Macro Q_OBJECT zu Beginn der Klassendeklaration. Da man QObject-Instanzen nicht kopieren soll, wird der Copy-Konstruktor und der Zuweisungsoperator mit dem Macro Q_DISABLE_COPY versteckt.

Kennzeichnend für die Pointer-Implementierung ist der Pimpl, ein einzelner privater Pointer, anstelle von einzelnen privaten Membern. Der Pimpl heißt bei Qt d_ptr für D-Pointer. Die private Klasse, auf die der D-Pointer zeigt, kapselt alle privaten Variablen und Methoden, kurz alles was nicht zur öffentlichen API der Klasse gehört. Der Typ der privaten Klasse wird nur deklariert und nicht etwa per Header eingebunden. Das erlaubt es jederzeit die internen Eigenschaften der Form-Klasse zu ändern, ohne deren Deklaration, also die Header-Datei, ändern zu müssen.

Mit dem Macro Q_DECLARE_PRIVATE vereinfachen wir den Zugriff auf den D-Pointer bei der Implementierung. Wichtig dabei ist, das der D-Pointer auch d_ptr heißt!

Zunächst die Implementierung der Klasse.

#include "form.h"
 
class FormPrivate
{
public:
    QWidget *header;
    QWidget *body;
    QWidget *footer;
};
 
Form::Form(QWidget *parent) :
    QWidget(parent),
    d_ptr(new FormPrivate(this))
{
}
 
Form::~Form()
{
    delete d_ptr;
}
 
QWidget *Form::header() const
{
    Q_D(const Form);
    return d->header;
}
 
void Form::setHeader(QWidget *header)
{
    Q_D(Form);
    d->header = header;
}
 
[...]

Zu Beginn der cpp-Datei wird die private Klasse deklariert. Die Deklaration kann auch in einer eigenen Datei erfolgen die dann das Suffix _p hat, für das Form-Beispiel also form_p.h. Die private Klasse besitzt alle Member die die öffentliche Klasse benötigt. Im Beispiel sind das nur die drei Variablen header, body und footer. Es können aber auch beliebig Methoden definiert werden. Der Einfachheit halber und weil die private Klasse FormPrivate nur von Form genutzt wird, sind die Member alle public.

Der Konstruktor der öffentlichen Klasse initialisiert den D-Pointer. Analog dazu zerstört der Destruktor den D-Pointer. Am Beispiel der header-Eigenschaft zeige ich wie man nun mit D-Pointer arbeitet. Das Macro Q_D stellt die Variable d für den Zugriff auf den D-Pointer zur Verfügung.

Die beiden Macros Q_DECLARE_PRIVATE im Header und Q_D in der Implementierung arbeiten zusammen. Hier im Beispiel ersparen sie nur ein wenig Tipparbeit. Bei Objektstrukturen mit Vererbung sorgen sie außerdem, dass an die private Klasse erweitern kann. Die dafür notwendigen Casts führen die Macros automatisch durch, so das der Zugriff auf den D-Pointer in abgeleiteten Klassen genauso einfach ist wie in der Basisklasse.

Schreibe einen Kommentar

Deine E-Mail-Adresse wird nicht veröffentlicht. Erforderliche Felder sind mit * markiert