Wo C++ von C geschlagen wird
« | 08 Nov 2020 | »Bjarne Stroustrup hatte bei der Schaffung der Sprache C++ einen Grundsatz mitaufgestellt:
Es soll unterhalb von C++ keinen Bedarf für eine andere Low-Level Sprache geben.
Mit anderen Worten:
C ist keine notwendige Untermenge von C++
Doch da gibt es so ein paar Dinge, die das gute alte C “besser” kann.
Das GATE Projekt lehrt mich in vielen Bereichen, wie unfassbar einfacher es in C++ ist, Code zu schreiben als in reinem C. Ich verbringe viele Stunden mit dem Tippen von Zeilen, die in C++ dank Operatoren, Konstruktoren und Destruktoren nur kleine Einzeiler wären.
Besonders Objekte und Polymorphie hat C++ nativ drauf, wo in C die Deklaration von virtuellen Methodentabellen und deren Implementierung pro Objekt von Hand ausgearbeitet werden müssen.
Früher bestand daher der Bedarf nach C vor allem bei Performance-kritischen Programmteilen. C++ generierte zusätzlichen Metacode, der an die Einfachheit von C nicht herankam. Doch auch diese Domäne eroberten neue hoch-optimierte Compiler, womit Bjarne Stroustrups Zielsetzung erfüllt schien.
Eine Ausnahme gibt es (aus meiner Sicht) aber:
Statische Daten und Objekte
Ein static char const*
hat gegenüber einem static std::string const
vor
allem den Vorteil, dass die Daten im Datensegment liegen, während beim C++
Objekt ein Konstruktor aufgerufen wird und die Daten auf den Heap (oder in
einen anderen Bereich) kopiert werden.
Dieses Problem soll constexpr
im neuesten Standard beheben, doch wenn es
um Objekte, Vererbung und Instanzen geht, kommt man bei C++ einfach nicht
darum herum, dass Code ausgeführt werden muss um Daten zu initialisieren.
Und sobald ein störrisches if
in einer Methode ist, verliert constexpr
seine Wirkung.
In C lässt sich aber ein Fall so hinbiegen, dass ein Objekt samt V-Table und Daten statisch deklariert wird und ohne “Konstruktion” wie ein reguläres Objekt nach außen erscheint.
Natürlich wäre ein solcher C Code automatisch auch mit C++ kompilierbar, doch man wird von einem waschechten C++ Entwickler nie solche Zeilen erwarten.
Beispiel Factory-Schnittstellen
Die MicroService Implementierung des GATE Projektes erfordert Factory-Objekte, die die eigentlichen Dienste bei Bedarf dynamisch erzeugen. Ein Codemodul oder Plugin exportiert einfach eine Factory, die den Anforderungen des Servicehosts entsprechen.
Eine solche Factory ist (ganz analog zu COM oder einem virtuellen C++ Objekt) ein Pointer zu einem Objekt. Dieses enthält eine Schnittstelle für Referenzzählung und somit wäre die übliche Implementierung auf den Heap zugeschnitten.
Doch in C können wir ein solches Objekt auch ganz einfach als globales Objekt
in statische Variablen packen. Diese struct
wird dann global initialisiert
was zur Folge hat, dass ein Abbild des Objektes einfach im Datensegment landet.
Es ist kein Konstruktor und kein Heap notwendig und wir können Pointer auf diese eine globale Factory mehrfach “verkaufen”. Die Referenzzählung hinter der Schnittstelle findet einfach nicht statt, so wie auch keine Löschung durchgeführt wird … denn es sind nur statische Daten.
Ein Code sagt mehr als tausend Worte:
1typedef struct my_vtbl 2{ 3 int (*my_method)(void* thisptr, int my_param); 4} my_vtbl_t; 5 6typedef struct my_implementation 7{ 8 my_vtbl_t const* vtbl; 9 10 int some_globale_data; 11} my_implementation_t; 12 13static int my_method_implementation(void* thisptr, int my_param) 14{ 15 my_implementation_t* self = (my_implementation_t)thisptr; 16 /* do something */ 17 return 0; 18} 19 20static my_vtbl_t const my_vtbl = 21{ 22 &my_method_implementation 23}; 24 25static my_implementation_t global_instance = 26{ 27 &my_vtbl, 28 123456 29}; 30 31my_implementation_t* create_object() 32{ 33 return &global_instance; 34}
Fazit
Natürlich ist viel Schreibarbeit erforderlich, doch das Resultat ist ein
garantiert statisches Konstrukt, das kein Byte verschwendet.
Dadurch ist es Mikrocontroller-tauglich und kann die Idee von
Objektorientierung und Schnittstellen auch in Bereiche hineintragen, in
denen C++ Compiler zu verschwenderisch vorgehen.