Forum: PC-Programmierung Frage zu Interfaces in C++


von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Hallo Forum,

mir geht es vornehmlich darum, ob ich Interfaces in C++ soweit 
verstanden habe.

Also:

Man definiert ein Interface, hier als Beispiel die (abstrakte?) Klasse 
IHardware
1
class IHardware {
2
    public:
3
        virtual bool getPinState(uint8_t pin_um) = 0;
4
        ...
5
};

Dann braucht man eine Klasse die dieses Interface implementiert, hier 
die Klasse Hardware
1
class Hardware : public IHardware {
2
    public:
3
        bool getPinState(uint8_t pin_num);
4
        ...
5
};
6
7
bool Hardware::getPinState(uint8_t pin_num) {
8
    bool state = ....
9
    return state;
10
};

Als drittes braucht man noch etwas, was auf dieses Interface zugreift, 
z.B. eine Funktion
1
void accessHardware(IHardware& hw) {
2
    bool state = hw.getPinState(5);
3
    ...
4
}

Zusammengeklatscht könnte das ganze so aussehen:
1
int main(void) {
2
    Hardware HwObj;
3
    ...
4
    accessHardware(HwObj);
5
    ...
6
    return 0;
7
}

Dadurch hab ich die Möglichkeit, z.B. dem Objekt HwObj ein neues 
Verhalten zu gegebn, in dem ich z.B. gegen eine neue Klasse Hardware 
(welches auch das Interface IHardware implementiert), welche einen Mock 
darstellt, baue.

Weiterer Vorteil ist, dass man in die Funktion accessHardware alle 
Objekte reingeben kann, die das Interface IHardware implementieren.

Ist das soweit richtig?

Grüße

von Slippin J. (gustavo_f)


Lesenswert?

Kaj G. schrieb:
> Dadurch hab ich die Möglichkeit, z.B. dem Objekt HwObj ein neues
> Verhalten zu gegebn, in dem ich z.B. gegen eine neue Klasse Hardware
> (welches auch das Interface IHardware implementiert), welche einen Mock
> darstellt, baue.

Neues Verhalten einem Objekt hinzufügen, macht man eher über 
Komposition.

Kaj G. schrieb:
> Weiterer Vorteil ist, dass man in die Funktion accessHardware alle
> Objekte reingeben kann, die das Interface IHardware implementieren.
>
> Ist das soweit richtig?

Ja, das ist ein großer Vorteil der Interface-Vererbung. Deine konkrete 
Klasse Hardware, kann beliebig komplex sein. Du kannst auch ganz andere 
Klassen verwenden (z.B. Maus, Monitor, CPU). Für accessHardware ist nur 
relevant dass sie von IHardware abgeleitet sind.

von Wilhelm M. (wimalopaan)


Lesenswert?

Kaj G. schrieb:

> Dann braucht man eine Klasse die dieses Interface implementiert, hier
> die Klasse Hardware
> [c]
> class Hardware : public IHardware {
>     public:
>         bool getPinState(uint8_t pin_num);
>         ...
> };

Hier könntest Du noch ein "override" hinzufügen, damit Du nicht aus 
Versehen die Elementfunktion re-definierst.

Ausserdem sollten Getter (also Observator-Funktionen) immer "const" 
sein.

> Dadurch hab ich die Möglichkeit, z.B. dem Objekt HwObj ein neues
> Verhalten zu gegebn, in dem ich z.B. gegen eine neue Klasse Hardware
> (welches auch das Interface IHardware implementiert), welche einen Mock
> darstellt, baue.

Da verstehe ich nicht genau was Du meinst: generell beschreibt das 
Interface eine "Verhaltenseigenschaft". Die Datenelemente den (internen) 
Zustand.

> Weiterer Vorteil ist, dass man in die Funktion accessHardware alle
> Objekte reingeben kann, die das Interface IHardware implementieren.

Vielleicht beachtest Du auch die Art der Parameterübergabe: Du übergibt 
als non-const reference: das bedeutet dann semantisch ein 
Input-Output-Parameter. Ist das so gewünscht?

: Bearbeitet durch User
von tictactoe (Gast)


Lesenswert?

Kann man so machen. Ist halt so wie man es unter Java machen würde.

Unter C++ wäre "IHardware" nur ein Denkkonstrukt. Die Implementierung in 
der Klasse Hardware würde es genau so geben, nur eben ohne virtuelle 
Funktionen. Und immer wenn es eine Funktion gibt, an die das Objekt 
HwObj weitergereicht wird, wäre eine Template-Funktion. Damit fällt die 
ganze Indirektion weg und der Compiler kann optimieren bis zum 
gehtnichtmehr.

Das Konstrukt "IHardware" besteht dann nur mehr in der Dokumentation der 
Template-Funktionen: "Der Parameter xy muss die Eigenschaft erfüllen, 
dass xy.getPinState(num) ein gültiger Ausdruck ist."

von Wilhelm M. (wimalopaan)


Lesenswert?

tictactoe schrieb:
> Kann man so machen. Ist halt so wie man es unter Java machen würde.
>
> Unter C++ wäre "IHardware" nur ein Denkkonstrukt. Die Implementierung in
> der Klasse Hardware würde es genau so geben, nur eben ohne virtuelle
> Funktionen.

Wenn es denn um eine Anwendung im uC-Bereich geht: ja.

>
> Das Konstrukt "IHardware" besteht dann nur mehr in der Dokumentation der
> Template-Funktionen: "Der Parameter xy muss die Eigenschaft erfüllen,
> dass xy.getPinState(num) ein gültiger Ausdruck ist."

Man kann diese Anwendung der statischen Polymorphie auch direkt in C++ 
ausdrücken mit Hilfe des CRTP-Patterns. Denn Dokumentation und Code 
driftet leider häufig auseinander.

von Hannes J. (Firma: _⌨_) (pnuebergang)


Lesenswert?

Kaj G. schrieb:
> Ist das soweit richtig?

Vom Prinzip her geht es um Typ-Kompatibilität.

Die klassische Art in vielen OO-Sprachen (aber nicht allen) einen zu 
einem anderen Typ kompatiblen Typ zu erhalten ist durch Ableiten einer 
Klasse. Das schränkt aber insoweit ein, dass alle zu einem Typen 
kompatibel Typen Unterklassen einer Oberklasse sein müssen.

Mit Interfaces hat man diese Einschränkung nicht. Alle Klassen die das 
Interface implementieren sind typ-kompatibel, müssen aber nicht mehr 
alle von der gleichen Oberklasse abgeleitet sein.

Ein weiterer Vorteil von Interfaces ist dass man mehrere Interfaces in 
einer Klasse implementieren kann und die Klasse damit kompatibel zu 
mehreren Typen ist. Damit vermeidet man Mehrfachvererbung.

von Wilhelm M. (wimalopaan)


Lesenswert?

Hannes J. schrieb:
> Kaj G. schrieb:
>> Ist das soweit richtig?
>
> Vom Prinzip her geht es um Typ-Kompatibilität.

Das nennt sich Inklusionspolymorphie bzw. Liskovs'sches 
Substitutionsprinzip, falls man mal googlen möchte.


> Mit Interfaces hat man diese Einschränkung nicht. Alle Klassen die das
> Interface implementieren sind typ-kompatibel, müssen aber nicht mehr
> alle von der gleichen Oberklasse abgeleitet sein.

In C++ sind Interfaces Klassen ohne Zustand und nur mit rein-virtuellen 
Elementfunktionen und einem virtuellen Destruktor.

> Ein weiterer Vorteil von Interfaces ist dass man mehrere Interfaces in
> einer Klasse implementieren kann und die Klasse damit kompatibel zu
> mehreren Typen ist. Damit vermeidet man Mehrfachvererbung.

In C++ implementiert man mehrere Interfaces durch öffentliche 
Mehrfachvererbung, anders geht es nicht.

von Mark B. (markbrandis)


Lesenswert?

Hannes J. schrieb:
> Mit Interfaces hat man diese Einschränkung nicht. Alle Klassen die das
> Interface implementieren sind typ-kompatibel, müssen aber nicht mehr
> alle von der gleichen Oberklasse abgeleitet sein.

Das ist meines Wissens nach z.B. in Java so, aber nicht in C++. Oder?

von Wilhelm M. (wimalopaan)


Lesenswert?

Mark B. schrieb:
> Hannes J. schrieb:
>> Mit Interfaces hat man diese Einschränkung nicht. Alle Klassen die das
>> Interface implementieren sind typ-kompatibel, müssen aber nicht mehr
>> alle von der gleichen Oberklasse abgeleitet sein.
>
> Das ist meines Wissens nach z.B. in Java so, aber nicht in C++. Oder?

"Implementieren" bedeutet hier, von dem Interface-Type ableiten.

von Kaj G. (Firma: RUB) (bloody)


Lesenswert?

Wilhelm M. schrieb:
> Vielleicht beachtest Du auch die Art der Parameterübergabe: Du übergibt
> als non-const reference: das bedeutet dann semantisch ein
> Input-Output-Parameter. Ist das so gewünscht?
Das ist ja hier nur ein Beispiel, aber danke fuer den Hinweis. :)
const waere aber angebracht, da hast du recht.

Wilhelm M. schrieb:
>> Dadurch hab ich die Möglichkeit, z.B. dem Objekt HwObj ein neues
>> Verhalten zu gegebn, in dem ich z.B. gegen eine neue Klasse Hardware
>> (welches auch das Interface IHardware implementiert), welche einen Mock
>> darstellt, baue.
>
> Da verstehe ich nicht genau was Du meinst: generell beschreibt das
> Interface eine "Verhaltenseigenschaft". Die Datenelemente den (internen)
> Zustand.
Damit meine ich sowas wie Dependency Injection fuer Unittest.

Wilhelm M. schrieb:
> Hier könntest Du noch ein "override" hinzufügen, damit Du nicht aus
> Versehen die Elementfunktion re-definierst.
Ah, okay. Dafuer ist das Ding also gut :D

tictactoe schrieb:
> Und immer wenn es eine Funktion gibt, an die das Objekt
> HwObj weitergereicht wird, wäre eine Template-Funktion. Damit fällt die
> ganze Indirektion weg und der Compiler kann optimieren bis zum
> gehtnichtmehr.
Werde ich mir mal anschauen. Hoert sich interessant an.

Wilhelm M. schrieb:
> In C++ sind Interfaces Klassen ohne Zustand und nur mit rein-virtuellen
> Elementfunktionen und einem virtuellen Destruktor.
Warum ist der virtuelle Destruktor so wichtig?

Gruesse

von Wilhelm M. (wimalopaan)


Lesenswert?

Kaj G. schrieb:
>
> Wilhelm M. schrieb:
>> In C++ sind Interfaces Klassen ohne Zustand und nur mit rein-virtuellen
>> Elementfunktionen und einem virtuellen Destruktor.
> Warum ist der virtuelle Destruktor so wichtig?


Damit bei einem "delete" über einen BasiklassenZeiger nicht der 
Basis-dtor sondern der dtor des tatsächlichen Typs aufgerufen wird. Das 
ist absolutes MUSS!

von Rolf M. (rmagnus)


Lesenswert?

Hannes J. schrieb:
> Die klassische Art in vielen OO-Sprachen (aber nicht allen) einen zu
> einem anderen Typ kompatiblen Typ zu erhalten ist durch Ableiten einer
> Klasse. Das schränkt aber insoweit ein, dass alle zu einem Typen
> kompatibel Typen Unterklassen einer Oberklasse sein müssen.
>
> Mit Interfaces hat man diese Einschränkung nicht. Alle Klassen die das
> Interface implementieren sind typ-kompatibel, müssen aber nicht mehr
> alle von der gleichen Oberklasse abgeleitet sein.

Dafür müssen sie das Interface implementieren. Wo ist denn für dich der 
elementare Unterschied zwischen dem Implementieren eines Interfaces und 
dem Ableiten von einer abstrakten Basisklasse? Für mich sind das nur 
zwei verschiedene Bezeichnungen für ein und das selbe.

> Ein weiterer Vorteil von Interfaces ist dass man mehrere Interfaces in
> einer Klasse implementieren kann und die Klasse damit kompatibel zu
> mehreren Typen ist. Damit vermeidet man Mehrfachvererbung.

Auch hier die Frage: Wo außer beim Namen ist da der Unterschied? 
Abgesehen vielleicht davon, dass Mehrfachvererbung flexibler ist, was 
für mich aber kein Grund ist, es vermeiden zu wollen.

: Bearbeitet durch User
von Markus L. (rollerblade)


Lesenswert?

Kaj G. schrieb:
> Damit meine ich sowas wie Dependency Injection fuer Unittest.
Ja. Eine Methode/Funktion, die ein Objekt vom Typ Interface 
hineingereicht bekommt, kann bzgl. dieses Objekts recht einfach und 
unabhängig verunittestet werden. Der Unittest kann seine 
Testanforderungen in seiner eigenen Interface-Implementierung 
formulieren. Und es besteht weder in der produktiven Methode/Funktion 
noch im Unittest eine Anhängigkeit zu irgendeiner produktiven 
Interface-Implementierung, die nicht Bestandteil der zu vertestenden 
Unit ist.

Logisch ist dann auch, daß das Interface zur 
Komponente/Library/Framework gehört, die es verarbeitet, das vom User 
dieser/dieses Komponente/Library/Frameworks zu implementieren ist. Das 
nennt sich Entkoppelung. Die/das Komponente/Library/Framework darf seine 
User nicht kennen. Einmal fertiggestellt, verprobt und dann in eine Lib 
gepackt, ausgeliefert und, bis neue Anforderungen/Bug-Reports kommen, 
nicht mehr angefaßt.

: Bearbeitet durch User
Bitte melde dich an um einen Beitrag zu schreiben. Anmeldung ist kostenlos und dauert nur eine Minute.
Bestehender Account
Schon ein Account bei Google/GoogleMail? Keine Anmeldung erforderlich!
Mit Google-Account einloggen
Noch kein Account? Hier anmelden.