Zitat von
The_Student
In den Quellen von Qt4 und von der Coin3D-Bibliothek habe ich mittlerweile auch private header und klassen gefunden, verstehe aber noch nicht genau wie man das nun richtig umsetzt (die beiden Projekte sind zum "durchlesen" einfach zu gross).
Das nennt man auch "d-pointer" oder "pimpl" (pointer to implementation) Idiom.
Die Grundidee ist, alle Datenmembers einer Klasse und Teile der Funktionalität an eine andere Klasse zu delegieren, deren Deklaration aber nur sehr lokal bekannt ist, eben "privat".
Eingesetzt wird es hauptsächlich, um bei stabiler ABI (binäres Interface der Klasse) dennoch Erweiterungen machen zu können.
Beispiel:
Code:
// Im Header
class Point
{
public:
Point();
void setX(int x);
void setY(int y);
int x() const;
int y() const;
private:
int m_x;
int m_y;
};
// Im Source
Point::Point() : m_x(0), m_y(0) {}
void Point::setX(int x) { m_x = x; }
void Point::setY(int y) { m_y = y; }
int Point::x() const { return m_x; }
int Point::y() const { return m_y; }
Wenn du nun zu einem späteren Zeitpunkt gerne eine Methode hättest, mit der du Prüfen kannst, ob der Punkt "leer" ist oder echte Werte enhält, könntest du das so lösen
Code:
class Point
{
public:
bool isValid() const;
private:
bool m_valid;
};
Point::Point() : m_x(0), m_y(0), m_valid(false) {}
void Point::setX(int x) { m_x = x; m_valid = true; }
void Point::setY(int y) { m_y = y; m_valid = true; }
bool Point::isValid() const { return m_valid; }
(Rest des Codes bleibt gleich)
D.h. du brauchst eine neue Methode und einen neuen Member (m_valid)
Das hinzufügen des Members ist aber nicht binärkompatibel, weil die Größe der Klasse anwächst.
In einem d-pointer Szenario sieht die Ausgangsgrundlage so aus:
Code:
// Im Header
class Point
{
public:
Point();
~Point();
void setX(int x);
void setY(int y);
int x() const;
int y() const;
private:
class Private;
Private* d;
};
// Im Source (oder in einem privaten Header)
class Point::Private
{
public:
Private() : m_x(0), m_y(0) {}
public:
int m_x;
int m_y;
};
// Im Source
Point::Point() : d(new Private()) {}
Point::~Point() { delete d; }
void Point::setX(int x) { d->m_x = x; }
void Point::setY(int y) { d->m_y = y; }
int Point::x() const { d->return m_x; }
int Point::y() const { d->return m_y; }
D.h. die Daten sind in der privaten Klasse Point::Private, die Klasse Point hat nur einen einzigen Member, nämlich einen Pointer auf die Instanz von Point::Private;
Wie im ersten Beipiel jetzt die Änderung für Valid
Code:
class Point
{
public:
bool isValid() const;
};
class Point::Private
{
public:
Private() : m_x(0), m_y(0), m_valid(false) {}
public:
bool m_valid;
};
void Point::setX(int x) { d->m_x = x; d->m_valid = true; }
void Point::setY(int y) { d->m_y = y; d->m_valid = true; }
bool Point::isValid() const { return d->m_valid; }
Die Klasse Point bekommt nur eine neue Methode. Da sie nicht virtuell ist, ist das binär kompatibel. Der neue Member geht in die nach außen unbekannte/unsichtbare Private Klasse.
In diesem Fall ist es wichtig, dass man an ein paar Auswirkungen denkt:
- man braucht einen Copy Constructor und Zuweisungsoperator oder muss sie deaktivieren
- die Indirektion hat eine Runtime Overhead (Pointer muss dereferenziert werden)
- auch wenn eine Point Instanz auf dem Stack angelegt wird, ihr Private Teil ist immer im Heap
Für so eine primitive Klasse wie Point würde man das daher normalerweise nicht machen, aber bei komplexeren Aufgaben oder Abhängigkeiten kann die wesentliche Grundlage sein, eine Bibliothek länger (binär) stabil halten zu können.
Ciao,
_
Lesezeichen