PDA

Archiv verlassen und diese Seite im Standarddesign anzeigen : Private Header und Klassen in C++ projekten



The_Student
15-05-2008, 09:40
Hallo,

Ich befasse mich im Moment intensiv mit der sinnvollen Strukturierung von grösseren C++ Projekten. Bei Bibliotheken geht es ja immer wieder darum eine vernünftige Schnittstelle zu Verfügung zu stellen die sich nicht ändert und die eigentliche Implementierung vor dem Anwender zu verstecken. Ich bin in diversen Büchern (Stroustrup, Meyers) über ein paar Ansätze gestolpert die das erwähnen aber für mich nicht vollkommen verständlich erklären. 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).

Frage: Kann mir jemand ein Tutorial nennen oder ein kurzes Beispiel geben wie das nun ganz konkret mit der Trennung von Interface und Implementierung aussieht? Hat jemand einen guten Literatur-Tipp auf Lager?

Würde mir sehr weiter helfen.
Danke schonmal.

BLUESCREEN3D
15-05-2008, 14:07
Ich weiß nicht, was du mit "privaten Headern" meinst, aber die Trennung von Interface und Implementierung geht in der objektorientierten Programmierung üblicherweise so, dass du eine abstrakte Klasse (das Interface) hast, die die Signaturen aller wichtigen Methoden enthält.
Die Implementierung ist dann eine andere Klasse, die von der Interface-Klasse abgeleitet ist (Vererbung).
Das tolle daran ist, dass es mehrere Implementierungen geben kann und dass die einfach ausgetauscht oder dynamisch ausgewählt werden können, ohne viel im Programm zu ändern. Deshalb nutzt man dann im Programm auch die Interfaceklasse als Datentyp für Instanzen der Implementierungsklasse.

Hier steht das nochmal mit Beispielen zu Jave: http://de.wikipedia.org/wiki/Schnittstelle_%28objektorientierte_Programmierung% 29

In C++ schreibt man Interfaces mit rein virtuellen Methoden (das sind die mit "= 0" am Ende).
Das könnte dann z.B. so aussehen:

class ein_interface
{
public:
virtual ~ein_interface()
{
}

virtual void bla() = 0;
};

class eine_implementierung : public ein_interface
{
public:
virtual void bla()
{
//inhalt
}
};

int main()
{
//anwendung:

ein_interface *irgendwas = new eine_implementierung;

irgendwas->bla();

delete irgendwas;
}

Literatur: Sowas sollte eig. in jedem Buch über Software Engineering stehen. Such ansonsten nach "interface", "program to an interface"
Angewendet werden solche Trennungen z.B. ständig in dem Buch "Design Patterns", aber ich würde dir erstmal empfehlen, ein Buch über Software Engineering zu lesen.

Da du auch vom Verstecken der Implementierung geredet hast: Das ganze nennt sich Information Hiding - siehe z.B. http://de.wikipedia.org/wiki/Datenkapselung_%28Programmierung%29 - und ist noch etwas anderes. Dabei könnte man in gewisser Weise aber die public-Methoden einer Klasse als "Interface" bezeichnen.

The_Student
15-05-2008, 16:25
Hallo und ganz herzlichen Dank für Deine Antwort!

Genauso wie Du es beschreibst, habe ich das auch aufgefasst und in "Die C++ Programmiersprache" von Stroustrup (soweit ich weiss DIE BIBEL für C++-Programmierer :-) ) ist das auch ganz nett beschrieben. Nur fehlt mir dafür ein konkreteres Beispiel. Wie bereits oben beschrieben habe ich durchaus ein paar Beispiele in Qt finden können. Allerdings ist das Projekt mir dann wieder zu Gross um einen wirklich guten Überblick bekommen zu können.

Ich hatte gehofft, das jemand ein paar ganz konkrete Beispiele in irgendwelchen kleineren Opensourche-Projekten o.ä. nennen könnte da ich das noch nicht ganz durchschaut hab bzw. ich es noch nicht so verinnerlicht habe das ich es im Schlaf implementieren könnte.

anda_skoa
16-05-2008, 14:31
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:


// 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

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:


// 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


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,
_

The_Student
16-05-2008, 15:32
Herzlichen Dank für die Antwort! Das Prinzip war mir so klar, aber nicht soooo klar :-). Besonders das Argument mit der Binärkompatiblität kannte ich noch nicht. Jetzt muss ich mich nur noch entscheiden ob ich diese Methodik wirklich brauche oder nicht...