Forum: Compiler & IDEs C sorgt oft für Verwirrungen


von Yalu X. (yalu) (Moderator)


Lesenswert?

Dr. Sommer schrieb:
> Naja das Ziel von C++ ist ja nicht Haskell zu imitieren...

Das nicht, aber:

C++ ist eine sehr mächtige Sprache. Dass es kaum etwas gibt, was nicht
geht, erkennt man am besten, wenn man sich die Boost-Bibliothek(en)
anschaut. Dabei stecken in den meisten dieser Bibliotheken nicht einmal
komplexe Algorithmen, es werden einfach nur Sprachkonstrukte, die in
jedem C++-Buch beschrieben sind, zusammengesteckt.

Trotzdem sind die allerwenigsten C++-Programmierer in der Lage zu
verstehen, wie diese Bibliotheken funktionieren, geschweige denn, etwas
ähnliches selber zu schreiben. Selbst erfahrene C++-Programmierer
brauchen einige Zeit, um so ein Geflecht von Template-Konstrukten
vermischt mit Klassenhierarchien, überladenen Funktion usw. zu
durchschauen.

Schaut man sich hingegen Code vergleichbarer Funktionalität in einer
Python-Bibliothek an, hat man oft innerhalb weniger Minuten ein
Aha-Erlebnis und ist anschließend nicht nur befähigt, sondern auch
motiviert, selber ähnlich coole Dinge zu stricken.


C++ liefert prinzipiell alle Voraussetzungen für die generische
Programmierung. Gute Beispiele für deren Nutzung sind die STL und auch
hier wieder die Boost. Generische Programmierung ist die Basis für die
Wiederverwendbarkeit von Softwaremodulen, weswegen eigentlich jeder
professionelle Programmierer, wo immer möglich, generisch programmieren
sollte.

Leider ist die generische Programmierung in C++ mit nicht unerheblicher
zuätzlicher Schreibarbeit verbunden, macht den Code unübersichtlich und
erschwert das Debuggen. Deswegen werden Dinge meist nur dann generisch
programmiert, wenn zu Beginn der Entwicklung bereits feststeht, dass sie
auch in anderen Projekten verwendet werden sollen. Für den ganzen Rest
gilt: Falls etwas davon irgendwann erneut benötigt wird, wird der Code
kopiert und so lange hingebogen, bis er passt. Weil die erforderlichen
Änderungen aber oft über große Teile des Codes verstreut sind, läuft es
oft genug darauf hinaus, dass dieser neu geschrieben wird.

In Python und Haskell hat jede noch so schnell hingerotzte Funktion
schon eine gewisse Grundgenerizität. Schreibt man bspw. in Python
folgende Funktion, die aus einer Liste von Integer-Zahlen die am
häufigsten vorkommende Zahl ermittelt
1
def haeufigst(werte):
2
  [(wert, n)] = Counter(werte).most_common(1)
3
  return wert

und benötigt irgendwann später eine Funktion, die das häufigste Zeichen
in einem Textstring ermittelt, stellt man fest, dass man die Funktion
überhaupt nicht anpassen muss. In C++ müsste man zu diesem Zweck die
entsprechende Funktion als Template implementieren, was aber kein Mensch
macht, wenn es dafür keinen dringenden Grund gibt.


Ich finde es schade, dass in einer Sprache, die so viele Möglichkeiten
bietet wie C++, so wenige davon im realen Leben tatsächlich genutzt
werden. Und wenn es doch einmal jemand tut, gilt er gleich als C++-Nerd,
dessen Code man sich lieber nicht anschaut, weil man ihn ja sowieso
nicht verstehen wird. Man kann dieses Unverständnis aber auch niemandem
verübeln, denn C++ ist nun einmal eine sehr komplizierte Sprache.

Ein Weg aus diesem Dilemma besteht darin, dass man sich innerhalb einer
Entwicklergruppe auf eine Untermenge von C++ einigt, die jeder leicht
verstehen kann. Man erkauft sich diesen Ausweg aber mit langfristig
höheren Entwicklungskosten wegen schlechterer Widerverwendbarkeit des
entwickelten Codes.

Aber die (IMHO bessere) Alternative besteht darin, durch die gezielte
Weiterentwicklung der Sprache deren nützliche Features auch den weniger
gewieften Durchschnittsprogrammierern (zu denen ich auch gehöre) näher
zu bringen.

Und ich finde, C++ ist derzeit auf einem ganz guten Weg in diese
Richtung. Folgendes Beispiel zeigt eine generisch programmierte
Schleife, die für einen beliebigen Container-Typ dessen Elemente eines
beliebigen numerischen Typs aufsummiert:

Früher sah das schon etwas gruselig aus:
1
  for(typename T::const_iterator it=container.begin(); it!=container.end(); it++)
2
    s += *it;

Mit der Einführung der Typinferenz in C++11 fällt schon einmal der
schreckliche Typausdruck weg:
1
  for(auto it=container.begin(); it!=container.end(); it++)
2
    s += *it;

Und mit etwas syntaktischem Zucker, der ebenfalls von C++11 beigesteuert
wird, sieht das Ganze endlich wie ordentlicher Code aus:
1
  for(auto x : container)
2
    s += x;

Ich bin davon überzeugt, dass man mit einer erweiterten Typinferenz und
noch etwas mehr Zucker auch viele andere häufig vorkommende Konstrukte
übersichtlicher gestalten kann, so bspw. (wie in den Beispielen von Dr.
Sommer) auch mehrfache Funktionsrückgabewerte und Rückgabewerte, die
optional invalid sein können.


Das meinte ich, als ich oben schrieb:

> Da muss aber noch mehr gehen ;-)

Es wird mehr gehen! C++14 ist ja eher als so eine Art Zwischenversion
gedacht. Dewegen bin ich sehr gespannt, was dessen Nachfolgerversion
(C++2x?) bringen wird :)

von W.S. (Gast)


Lesenswert?

Yalu X. schrieb:
> Und mit etwas syntaktischem Zucker,...

Hör mal, Yalu, du hast dich da ziemlich verrannt. Glaube bitte NICHT, 
daß auch nur eines deiner Beispiele wirklich LESBAR ist. Die schiere 
Anzahl von hingetippten Zeichen zu verringern ist kein Beitrag zur 
besseren Lesbarkeit, sondern nur das Ansinnen, immer mehr 
Hintergrundwissen über implizite Dinge beim Leser vorauszusetzen. Aber 
genau DARAN hakt es, wie du ja selbst schriebest: Die meisten C++ 
Programmierer verstehen nur noch Teilmengen dessen, was in dieser 
Sprache formell möglich ist. Sag jetzt nicht, daß diese Programmierer ja 
bloß zu blöd seien. Ich hab da einen ganz anderen Standpunkt: Eine 
Zwecksprache, also eine Programmiersprache, sollte gefälligst ein 
verständliches und möglichst leicht zu gebrauchendes Mittel sein, das 
auszudrücken, was man bezwecken will. C und in gesteigertem Maße C++ 
sind das nicht, siehe diese Diskussion hier.

Der Knackpunkt der Diskussion scheint mir der durchaus abwegige Glaube 
zu sein, daß die universelle Wiederverwertbarkeit geschriebenen Codes 
das höchste Ziel sei. Das ist es nicht. Was willst du da eigentlich 
wiederverwenden? Einen algorithmus, der für einen bestimmten Zweck mit 
bestimmten Daten erdacht wurde für einen ganz anderen Zweck mit ganz 
anderen Daten? Das läuft darauf hinaus, eine eierlegende Wollmilchsau zu 
schreiben und sie später durch aufwendige Parametrisierung an die 
verschiedenen Stellen der realen Welt anzupassen. Was du bei der 
wiederverwendbaren Sau eingespart hast, geht bei der Parametrisierung 
doppelt und dreifach wieder drauf - und du hast den erzeugten Code so 
unübersichtlich gemacht, daß er schlecht verständlich und damit schlecht 
wartbar und bugträchtig ist.

Kurzum, ich verstehe ja das Bestreben, sich die eigene Arbeit zu 
optimieren, aber das stupide Wiederverwendenwollen eines 
Quellcodefetzens ist dies nicht, da wäre schon viel eher ein Fundus 
wohlkommentierter und gut verständlicher Algorithmen angesagt, die man 
in seinem persönlichen Portfolio hat und auch noch nach ein paar Jahren 
noch ohne Mühen nachvollziehen kann.

W.S.

von Dr. Sommer (Gast)


Lesenswert?

Yalu X. schrieb:
> Das nicht, aber:
Dem ist eigentlich nichts mehr hinzuzufügen, außer: C++ ist für 
"super-effiziente" und dabei abstrakte Programme gedacht, insbesondere 
auch Embedded, mit expliziter Speicherverwaltung etc. Das schränkt die 
Möglichkeiten der Sprache halt gegenüber z.B. Haskell drastisch ein und 
nutzt Tastaturen ab, ist aber für Low-Level Anwendungen mit 
deterministischer Speicherverwaltung noch (lange) unersetzbar...

W.S. schrieb:
> Yalu X. schrieb:
>> Und mit etwas syntaktischem Zucker,...
>
> Hör mal, Yalu, du hast dich da ziemlich verrannt. Glaube bitte NICHT,
> daß auch nur eines deiner Beispiele wirklich LESBAR ist.
Chinesisch ist für mich auch nicht lesbar. Man muss die Sprache 
natürlich können. Ich kann zwar kein Haskell aber andere funktionale 
Sprachen und konnte Yalu's Beispiele durchaus (halbwegs) lesen.

> Die schiere
> Anzahl von hingetippten Zeichen zu verringern ist kein Beitrag zur
> besseren Lesbarkeit, sondern nur das Ansinnen, immer mehr
> Hintergrundwissen über implizite Dinge beim Leser vorauszusetzen.
Oder den Blick auf die wesentliche Funktion des Codes nicht durch 
unnötig viel Syntax zu verstellen?!
> Aber
> genau DARAN hakt es, wie du ja selbst schriebest: Die meisten C++
> Programmierer verstehen nur noch Teilmengen dessen, was in dieser
> Sprache formell möglich ist.
Insbesondere du nicht, wie wir ja schon länger wissen.
> Sag jetzt nicht, daß diese Programmierer ja
> bloß zu blöd seien. Ich hab da einen ganz anderen Standpunkt: Eine
> Zwecksprache, also eine Programmiersprache, sollte gefälligst ein
> verständliches und möglichst leicht zu gebrauchendes Mittel sein, das
> auszudrücken, was man bezwecken will. C und in gesteigertem Maße C++
> sind das nicht, siehe diese Diskussion hier.
Stimmt prinzipiell, aber gewisse Umgebungen wie Embedded haben gewisse 
Anforderungen, und um die alle auszudrücken braucht man C oder C++. 
Andererseits kann man in C++ schon eine Menge mehr ausdrücken als zB in 
Pascal oder Java...
>
> Der Knackpunkt der Diskussion scheint mir der durchaus abwegige Glaube
> zu sein, daß die universelle Wiederverwertbarkeit geschriebenen Codes
> das höchste Ziel sei. Das ist es nicht.
Das ist deine Meinung.
> Was willst du da eigentlich
> wiederverwenden? Einen algorithmus, der für einen bestimmten Zweck mit
> bestimmten Daten erdacht wurde für einen ganz anderen Zweck mit ganz
> anderen Daten?
Siehe z.B. std::max, std::sort, std::transform, std::copy, 
std::partition etc. - Algorithmen die so universal sind, dass sie sogar 
in die Sprachdefinition aufgenommen wurden, und dank generischer 
Programmierung auf sehr viele Fälle angewendet werden können. Wenn man 
die Algorithmen also kennt kann man Anwendungs-Code, der sie verwendet, 
viel leichter lesen.
> Das läuft darauf hinaus, eine eierlegende Wollmilchsau zu
> schreiben und sie später durch aufwendige Parametrisierung an die
> verschiedenen Stellen der realen Welt anzupassen.
Hilfe, std::sort braucht EINEN GANZEN Typ-Parameter! Da benutzen wir 
lieber 100 einzelne Algorithmen, gell?
> Was du bei der
> wiederverwendbaren Sau eingespart hast, geht bei der Parametrisierung
> doppelt und dreifach wieder drauf - und du hast den erzeugten Code so
> unübersichtlich gemacht, daß er schlecht verständlich und damit schlecht
> wartbar und bugträchtig ist.
Wenn alle deine Versuche, Meta-Programmierung zu betreiben, so enden, 
ist das natürlich bedauerlich. Aber das heißt natürlich nicht, dass 
andere das nicht richtig könnten, und für die ist C++ (und andere 
Meta-Programmierung-fähige Sprachen) ein mächtiges Werkzeug.
> Kurzum, ich verstehe ja das Bestreben, sich die eigene Arbeit zu
> optimieren, aber das stupide Wiederverwendenwollen eines
> Quellcodefetzens ist dies nicht, da wäre schon viel eher ein Fundus
> wohlkommentierter und gut verständlicher Algorithmen angesagt, die man
> in seinem persönlichen Portfolio hat und auch noch nach ein paar Jahren
> noch ohne Mühen nachvollziehen kann.
Genau, den std::max Algorithmus sollte man sich viel lieber textuell auf 
eine Karteikarte schreiben, um ihn dann jedes Mal neu zu verstehen und 
in seinen Code für die einzelnen Typen neu zu implementieren.

Machen wir doch das Beispiel. Was findest du schöner:
1x zu programmieren:
1
template <typename T>
2
T max (const T& a, const T& b) {
3
  return a > b ? a : b;
4
}
Und diesen Code immer wieder zu verwenden, oder in jedem einzelnen 
Projekt für jeden einzelnen Typ zu schreiben:
1
int max (int a, int b) { return a > b ? a : b; }
2
unsigned int max (unsigned int a, unsigned int b) { return a > b ? a : b; }
3
short max (short a, short b) { return a > b ? a : b; }
4
float max (float a, float b) { return a > b ? a : b; }//etc. etc.
Aber Code-Wiederverwendbarkeit ist ja nicht so wichtig, mehr die 
Vermeidung ach so schwerer Dinge wie des "template" -Keywords. Und nein, 
die C-Schummler-Version
1
#define max(a,b) ((a)>(b)?(a):(b))
ist natürlich nicht gleichwertig, da sie zB bei Ausdrücken mit 
Seiteneffekten nicht funktioniert, keinen Scope hat etc.

von Klaus W. (mfgkw)


Lesenswert?

Das ist halt wie mit Besteck:
Wer zu grobmotorisch ist, sollte lieber stumpfe Messer nehmen zum Essen.
Wer ordentliches Werkzeug will, bevorzugt scharfe Messer.

von Pumpernickel (Gast)


Lesenswert?

TriHexagon schrieb:
> Die Frage ist doch, warum man nicht einen C und C++ Standard
> etabliert,
> der mal ordentlich ausmistet. Weg mit den Designfehlern und weg mit dem
> undefinierten Verhalten. Den alten Quellcode lässt sich dann immer noch
> mit den alten Standards kompilieren.
Es ist doch an Dir, Dir einen eigenen Satz zu definieren und es gibt ja 
innerhalb von Firmen auch Standards und Bibs.

Das Problem wie immer: Bringe mal alle Interessen unter einen Hut.

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.