Forum: Compiler & IDEs Benutzen von internen structs in Fremden Klassen


von RW (Gast)


Lesenswert?

Hallo,

ich möchte eine Treiberarchitektur entwickeln, bei der eine generische 
(hardwareunabhängige) Basisklasse den Großteil der Funktionalität 
implementiert und einer hardwarespezifische Klasse ein paar wenige 
Funktionen beisteuert.

Die generische Klasse soll im Wesentlichen nur ein paar Funktionsaufrufe 
auf die spezialisierte tätigen, wie auch die spezialisierte ein paar 
Funktionen der generischen nutzen wird.

Die Programmiersprache ist C++, der Compiler G++ und die Architekturen 
verschiedene.

Mein erster Gedanke war nun, die generische Klasse durch Vererbung von 
der hardwarespezifischen erweitern zu lassen. Dazu müsste ich ein paar 
virtuelle Methoden deklarieren, die dann von der hardwarespezifischen zu 
erweitern sind, etwa so:
1
class Driver {
2
  // ...
3
  void doSomething() {
4
    // ...
5
    doSpecific();
6
    // ...
7
  }
8
  virtual void doSpecific() = 0;
9
  // ...
10
};
11
12
class ATMega32Driver : public Driver {
13
  // ...
14
  virtual void doSpecific() {
15
    // ...
16
  }
17
};


Mit virtuellen Methoden möchte ich aber architekturbedingt sparsam 
umgehen, also kam mir als zweiter Gedanke, Templates einzusetzen.
1
class ATMega32Driver {
2
  void doSpecific() {
3
    // ...    
4
  }
5
};
6
7
8
template<typename HardwareType>
9
class Driver {
10
  HardwareType device;
11
12
  void doSomething() {
13
    // ...
14
    device.doSpecific();
15
    // ...
16
  }
17
};
18
19
Driver<ATMega32Driver> driver;

Dann würden die vtables wegfallen und man kann trotzdem noch genau 
vorgeben, welche Methoden im spezifischen Treiber enthalten sein sollen.

Nun habe ich in der spezialisierten Klasse außerdem noch ein struct 
Config. Das Layout von Config muss ich in der Klasse Driver nicht 
kennen, denn dort verwende ich nur Pointer und greife nicht auf dessen 
Member zu.

Allerdings kompiliert das Programm nicht, wenn ich in der Deklaration 
von Driver auf Config zugreife, etwa in der Form:
1
template<typename HardwareType>
2
class Driver {
3
  HardwareType device;
4
  HardwareType::Config* config;
5
  static HardwareType::Config* currentConfig;
6
7
  void doSomething() {
8
    // ...
9
    device.setup(config);
10
    device.doSpecific();
11
    // ...
12
  }
13
};

Der Compiler wird meckern: "type 'HardwareType' is not derived from type 
'Driver<HardwareType>'" und eben auf die Zeilen mit 
HardwareType::Config verweisen.

Meine Frage lautet nun allgemein, ob es eine elegante Lösung gibt, den 
hardwarespezifischen Teil vom hardwareunabhängigen zu trennen. 
Vielleicht kennt jemand einen guten Artikel zu der Thematik. Ich habe 
zum Beispiel diesen (http://www.hackcraft.net/cpp/templateInheritance) 
gefunden, aber der ist noch ein wenig dürftig und die Quellcodebeispiele 
nicht gerade hübsch.

Im Speziellen würde mich interessieren, warum ich bei der oben genannten 
Templateidee zwar Methodenaufrufe auf HardwareType tätigen kann, aber 
nicht keine Pointer auf dessen Membertypen (also HardwareType::Config*).

Vielen Dank für's Durchlesen.

von RW (Gast)


Lesenswert?

Letztere Frage hat mir gerade 
http://www.parashift.com/c++-faq-lite/templates.html#faq-35.18 recht gut 
beantwortet.

Der Compiler kann nicht davon ausgehen, dass HardwareType::Config* ein 
Typ ist, also muss man ihm das sagen:
1
template<typename HardwareType>
2
class Driver {
3
  HardwareType device;
4
  typename HardwareType::Config* config;
5
  static typename HardwareType::Config* currentConfig;
6
  // ...


Das scheint zu funktionieren. Wundervoll.

von netb (Gast)


Lesenswert?

Hallo RW,

ich setzte mich seit ca. einem Jahr mit einem ähnlichen Thema 
auseinander. Im Rahmen einer Studienarbeit habe ich versuche einen 
kompletten 8bit AVR µC als Klasse zu abstrahieren. Dabei besitzt diese 
Klasse Unterobjekte, welche die Hardwareeinheiten darstellen u.s.w.

Ich hatte dabei einige Probleme. Beispielsweise entstehen auf diese Art 
und Weise leicht Objekte mit tausenden Unterobjekten. Leider verbraucht 
jedes Objekt in C++ mindestens ein Byte an Speicher, auch dann wenn es 
keine Attribute sondern nur Methoden enthält. Ich habe alle diese 
Probleme umschiffen können und für den AtXMega128A1 eine (noch nicht 
vollständige) Klasse erstellt. Allerdings steht der Aufwand inzwischen 
in keinem Verhältnis zum Nutzen und die Programmierung ist nicht 
unbedingt wesentlich einfacher geworden dadurch - nur anders. Ich bin 
also etwas unzufrieden mit meiner Lösung.

Vielleicht kannst du mal kurz erläutern, was dein genaues Ziel ist. Wenn 
dein Ziel ähnlich meinem Ziel ist, könnte man diesbezüglich Erfahrungen 
austauschen. Vielleicht habe ich deine Absichten jedoch einfach nur 
Fehlinterpretiert und dann möchte ich dich mit meinen "Problemen" 
natürlich nicht nerven.

Bis dann,
netb

von RW (Gast)


Lesenswert?

Hallo,

nein, das ist nicht mein Ziel. Ich schreibe gerade einen SPI-Treiber für 
ein kleines Betriebssystem, das auf mehreren Plattformen läuft (Reflex, 
http://idun.informatik.tu-cottbus.de/reflex/). Das ganze System ist eine 
einzige Klasse, welche aus vielen anderen zusammengesteckt ist. Es ist 
eigentlich ziemlich effizient, aber bedarf einiger Einarbeitungszeit. 
Möglicherweise handelt es sich dabei um das, was du vorhast. Aber Reflex 
war eine Doktorarbeit ;-)

Bei meiner oben beschriebenen Architektur will ich einerseits 
vorschreiben, welche Funktionen man in der hardwarespezifischen 
Kindklasse des Treibers zu implementieren hat und von der Basisklasse 
aus darauf zugreifen, andererseits mag ich nicht gleich alles mit 
virtuellen Methodenaufrufen erschlagen. Vorhin bin ich auf das 
"Curiously Recurring Template Pattern" gestoßen, was meinen zweiten 
Ansatz von oben schön beschreibt. Es geht um statische Polymorphie in 
C++ mit Hilfe von Templates. Es ist unglaublich, was alles für 
abgefahrene Sachen in C++ möglich sind, ich bin ganz begeistert.

Beim Disassemblieren der Dateichen habe ich gemerkt, welchen Overhead 
virtuelle Methoden erzeugen können, wenn man nicht aufpasst. Darum ist 
C++ wahrscheinlich auch als ineffzient im eingebetteten Bereich 
verschrien.

von netb (Gast)


Lesenswert?

Ok, das ist natürlich schon etwas anderes, meine Idee war halt wirklich 
nur die reine Abstraktion der Hardware in Form von Objekten. Ich bin 
hellhörig geworden, als ich deine Klassenkonstruktion mit dem Template 
gesehen habe, was mich etwas an meine Lösungen dazu erinnert hat.

Auf virtuelle Methoden verzichte ich auch größtenteils, da diese beim 
AVR nicht nur Code-Overhead, sondern auch eine 
Datenspeicherverschwendung verursachen. Mit statischer Polymorphie habe 
ich mich damals nicht beschäftigt, aber das Curiously Recurring Template 
Pattern sieht in der Tat vielversprechend aus.

Was mich an Templates stört: Ein Template zieht ein anderes Template 
hinter sich her. Bsp:
Die Klasse A benutzt einen Zeiger auf die Treiberklasse Driver um 
darüber was zu machen. Allerdings muss A dann die genaue Ausprägung von 
Driver wissen, da sie ansonsten keinen Zeiger darauf erzeugen kann. 
Somit muss A auch ein Templateklasse sein.

Dann kommt noch hinzu, dass wenn du mehrere Ausprägungen von Driver hast 
für jede Ausprägung noch einmal alle Methoden von Driver angelegt 
werden. Bei größeren Methoden und vielen Ausprägungen wächst damit der 
Programmspeicherverbrauch sprunghaft an.

von RW (Gast)


Lesenswert?

Das ist in der Tat ein Problem auf das ich auch gestoßen bin. Ich 
umschiffe es damit, dass der Datenfluss in meinem System über 
Ereigniskanalobjekte läuft, die unabhängig vom konkreten Treiber sind. 
Das heißt, der Rest des Systems kennt nur diese Ereigniskanalobjekte und 
ruft nicht direkt Funktionen des Treibers auf.

Für generische Methoden die trotzdem noch von anderen Klassen benötigt 
werden, welche aber die konkrete Ausprägung meines Treibers nicht kennen 
sollen (also den Templateparameter), habe ich noch eine Mutterklasse vor 
die templatetisierte Klasse gesetzt. Allerdings dürfen diese Methoden 
nur im Kontext dieser Mutterklasse bleiben, getter und setter 
beispielsweise.

In meinem Falls funktioniert es jetzt prima, ist aber nicht gerade 
besonders hübsch.

Wenn jemand noch Links zu guten Artikeln über Treiberarchitekturen hat, 
immer her damit. Ich habe heute bereits extrem viel gelernt :)
Ansonsten hat sich die Frage von oben erledigt.


@netb: Viel Erfolg noch bei deiner Riesenklasse ;-)

von netb (Gast)


Lesenswert?

Könntest du das Konzept der Ereignisknoten kurz anreißen, damit ich mir 
ein Bild davon machen kann? Auch die Sache mit den Mutterklassen 
interessiert mich. Ich habe viel hin und her überlegt wie man die Sache 
mit der Templateausprägung lösen kann.

Dabei verfolge ich zwei Konzepte: Mit dem gettype-Operator kann man beim 
GCC den Typen eines Objektes zur Kompilierzeit ermitteln. Beispiel:

Ich habe eine Hardwareklasse für die 1. USART Einheit am Port E des 
XMegas. Auf das Objekt dieser Klasse kann über das Objekt "uC" 
zugegriffen werden. Also:
1
uC.USARTE1 ...

Dann habe ich eine Treiberklasse RS232, welche als Template den Typen 
der Hardwareklasse benötigt. Um die Treiberklasse mit der Hardwareklasse 
zu verbinden schreibe ich nun:
1
RS232<gettype(uC.USARTE1)> com1;

Wenn die Ausprägung absolut gar nicht bekannt ist auch nicht per gettype 
ermittelt werden kann, dann benutze ich spezielle Klassen als "Mantel" 
und nur einer virtuellen Methode.

Basisklasse:
1
class BaseDriver
2
{
3
  virtual void access(int type, int param) = 0;
4
5
  void doSomething(int param)
6
  {
7
    access(0, param);
8
  }
9
10
};

Davon abgeleitete Templateklasse:
1
template<typename HWCLASS>
2
class Driver 
3
{
4
  typename HWCLASS hwObject;
5
6
  virtuel void access(int type, int param)
7
  {
8
    switch(type)
9
    {
10
      case 0:
11
       doSomething(param);
12
       break;
13
14
      ...
15
16
      default:
17
       break;
18
    }
19
  }
20
21
  void doSomething(int param)
22
  {
23
    hwObject.doAnything(param);
24
  }
25
26
};

Nun lege ich irgendwo den Treiber an:
1
  Driver<gettype(uc.USARTE1)> driver;

An einer anderen Stelle bekomme ich einen Pointer des Objektes. Die 
Programmteile, die mit pDriver anstatt Driver arbeiten haben keinen 
blassen Schimmer mehr welcher Ausprägung die Klasse von Driver war.
1
  BaseDriver *pDriver = &driver;

Und kann nun über diesen Basispointer auf die Methoden der abgeleiteten 
Klasse zugreifen (Umweg über die virtuelle Methode).
1
  pDriver.doSomething(1);

(Syntax ist nicht geprüft, die Beispiele dienen nur zur Verdeutlichung.)

Das ist zwar ein sehr komplizierter Umweg aber in manch einer Situation 
die einzige Lösung, die mir eingefallen ist, um die Ausprägung der 
Templates nicht in jeden Winkel meines Programmes mittragen zu müssen.

Ich hoffe, dass ich hier nicht zu sehr vom eigentlichen Thema abweiche, 
wenn doch können wir das per Mail behandeln, in einen anderen Thread 
packen oder die Diskussion einfach einstellen.

Ich wünsche dir auch viel Erfolg bei deinem Treiber für Reflex.

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.