Wo C++ von C geschlagen wird

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 notwenige 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.

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 Mikrokontroller-tauglich und kann die Idee von Objektorientierung und Schnittstellen auch in Bereiche hineintragen, in denen C++ Compiler zu verschwenderisch vorgehen.


Wenn sich eine triviale Erkenntnis mit Dummheit in der Interpretation paart, dann gibt es in der Regel Kollateralschäden in der Anwendung.
frei zitiert nach A. Van der Bellen
... also dann paaren wir mal eine komplexe Erkenntnis mit Klugheit in der Interpretation!