Modernes C++ programmieren

Nov 18, 2014

Auto und for-Schleifen

Nein, man lernt tatsächlich nie aus. Seit C++11 können wir ja schreiben

vector data{};
...
for(auto e : data) { cout << e; };

Das ist schon ganz schön praktisch gegenüber for(vector it=data.begin();...) { cout << *it; }, hat aber den großen Nachteil, dass erstens e hier oimmer kopiert wird und zweitens Änderungen an e sich nicht in data auswirken.

Stimmt, und deshalb erzähle ich auch immer, dass man daran danken sollte, in solchen Schleifen zuerst immer zu prüfen, ob man stattdessen nicht auto& oder const auto& verwenden sollte:

vector<int> data{};
...
for(auto& e : data) { e += 1; };

Das verhindert Kopie und data wird auch wirklich modifiziert.

Jetzt werde ich gerade erinnert , dass das nicht immer reicht. Zum Beispiel kann es keine (echte) Referenz in einen vector<bool> geben, weil man einzelne Bits nicht adressieren (also referenzieren) kann. Das gäbe mit auto& einen Fehler.

Daher, jetzt meine neue Regel: Man probiere es mit auto&&. Das ist dann der Einsatz der Meyerschen Universellen Referenzen, soweit ich das erkennen kann:

vector<int> data{};
...
for(auto&& e : data) { e += 1; };

Im Effekt bekommt man eine &, wenn nötig und eine &&, wenn erlaubt. Die genaue Erklärung was passiert, folgt in einem Detailbeitrag. Bis dahin probiere ich diese neue Merkregel mal aus.

Nachtrag

In einem Vorschlagsdokument des C++-Standards steht das Dilemma beschrieben:

What should we do about this problem? We can teach users to write
for (auto& elem : range) or for (const auto& elem : range), but they’re
imperfect (for proxies or mutation, respectively), and they require novices to
be introduced to auto and references earlier than otherwise necessary.
As explained immediately below, for (auto&& elem : range) is superior, but
it’s terrible to teach to novices. Even experienced C++98/03 programmers can
find it difficult to learn how rvalue references, the template argument
deduction tweak, and reference collapsing interact to produce what Scott Meyers
calls “universal references” (see [1]), and auto&& is far less commonly seen
than T&& in perfect forwarding.

Andererseits sagt Herb Sutter in Back To Basics , dass man auto&& nicht für lokale Variablen verwenden soll. Da stimme ich im Prinzip auch zu, aber grüble, ob sein Gesagtes nicht bei diesen auto-for-Schleifen eine Ausnahme sind. Hauptsächlich im Bezug der Einfachheit allerdings.

“STL” gibt etwas mehr Erklärung hier und bezieht auch den kommenden C++-Standard (wahrscheinlich) mit ein, der…

Ausblick

…eine neue Syntax enthält, die genau diese Schreibweise abkürzt: Einfach den gesamten Typen in der Schleifendeklaration weglassen und der Compiler macht daraus automatisch auto&&:

vector<int> data{};
...
for(e : data) { e += 1; };

…in der (fernen) Zukunft (vielleicht).

Zusammenfassung

Mit all den Informationen, die ich nun habe, möchte es noch einmal zusammenfassen. Vor allem “STL” (oben) und Howard Hinnant konnten mir Dinge klären

Ab C++17 gilt: Verwende for(e :...) wann immer möglich.

vector<int> data{};
...
for(e : data) { e += 1; };

Bis dahin, mit C++11 alleine, gilt:

In 99.9% der Fälle und somit für alle Entwickler bis zum braunen Gürtel gilt (immernoch): Verwende for(auto &x: ...) oder for(const auto &x: ...) wenn Du kannst und kopiere nur wenn nötig:

vector<int> data{};
...
for(auto &e : data) { e += 1; };
...
for(const auto &e : data) { cout << e; };

Entwickler mit dem schwarzen Gürtel, und/oder innerhalb einer Bibliothek mit einem Funktionstemplate für den Typ von data, kann es sinnvoll sein for(auto &&x: ...) zu verwenden:

vector<int> data{};
template 
void func(T&& data) {
  ...
  for(auto &&e : data) { e += 1; };
  ...
}

Denn ob RValue-Ref oder LValue-Ref ist nur dann relevant, wenn data für seine Iteratoren (temporäre) Proxy-Objekte benötigt (wie vector<bool>). Selbst wenn man dieses Konstrukt außerhalb eines Funktionstemplate verwendet, wird man && nicht brauchen, sondern kann sich bewusst zwischen ~[const]~ auto& und [const] auto entscheiden.

Okt 08, 2013

Buch: A Tour of C++

Soso, da gibt es also ein neues Buch vom Erfinder: A Tour of C++, Bjarne Stroustrup.

Ich habe viele Stimmen gehört, die sein Standardwerk zum C++-Standard gut fanden, aber vielleicht war ich damals einfach zu früh.

bei Amazon

Ein schneller Blick in sein neues – viel schlankeres – Werk ist nach meinem Geschmack: Viel Überblick, einige Details. Ein wenig vermisse ich die Zusammenhänge und wie die Sprachelemente vernetzt verwendet werden, aber wie gesagt, es war bisher nur ein schneller Blick, ich werde das prüfen. Ich denke zu dem Preis und der “Dünne” macht man nichts falsch.

Okt 07, 2013

Swap-Operator?

Manuelles Swap

Elemente zu vertauschen ist an vielen Stellen eine gefährliche Operation. Zu Zeiten ohne Exceptions und ohne * Multithreading* war vertauschen noch einfach:

Typ temp = a;
a = b;
b = temp;

Aber was passiert hier genau:

  • Typ temp = a; – erzeugt eine neue Instanz und initialisiert sie mit a, per Copy ConstructorTyp(const Typ&).
  • a = b; – ruft den Zuweisungsoperator Typ& operator=(const Typ&) auf
  • b = temp; – ruft noch einmal den Zuweisungsoperator Typ& operator=(const Typ&) auf.
  • Weil es eine neue Instanz ist, wird temp später per Destruktor weggeräumt.

Problempunkte

Bei diesem Prozedere gibt es vor allem zwei Dinge zu beachten:

  • Die Ressourcen, die Typ hält, werden diese wegen des Copy C’tors einmal kopiert.
    • Schade, wenn dies zum Beispiel ein riesengroßer Speicherblock ist, zu dem vielleicht nur ein Pointer getauscht werden müsste
    • Manche Resourcen können nicht kopiert werden: Locks, Mutexe, Filehandles machen Probleme, etc. Dann funktioniert das ganze Vorgehen nicht.
  • Was, wenn b = temp eine Exception wirft? Der Zuweisungsoperator sollte das nicht, aber wenn man dessen Code nicht unter Kontrolle hat? Dann sind a und b nicht vertauscht, sondern identisch. Das alte a ist verloren.

Der bessere Weg

Daher wird seit jeher die Verwendung der swap-Funktion aus der Standardbibliothek empfohlen:

using std::swap;
swap(a, b);

oder seit C++11 auch einfach

std::swap(a, b);

denn die Namensauflösung der swap-Implementierung wurde leicht geändert (Details im Buch ;-) oder bei Stackoverflow).

Edit: Es ist ein Idiom, die swap-Funktion eine friend-Memberfunktion zu machen, auch dazu mehr bei Stackoverflow.

Auch swap soll – wie der Zuweisungsoperator (und andere Beteiligte) – keine Exceptions werfen. Und da für die swap -Implementierung empfohlen wird, die Member elementweise wieder mit swap zu vertauschen, würde statt der Kopie der Ressourcen * im Falle des großen Datenblocks nur der Pointer ge-swapped * im Falle der nicht-kopierbaren Ressource diese vertauscht, was gehen sollte.

Daher merke (seit jeher): Eigene Datentypen sollten swap implementieren.

Neuer Operator :=:

Um die Wichtigkeit von swap zu unterstreichen, gibt es den Vorschlag, swap als Operator :=: einzuführen. Das sähe dann so aus:

struct Typ {
    Data a;
    Feld b;
};
Typ& operator:=:(Typ& lhs, Typ& rhs) noexcept {
    lhs.a :=: std::move(rhs.a);
    lhs.b :=: std::move(rhs.b);
    return lhs;
}
Typ& operator:=:(Typ& lhs, Typ&& rhs) noexcept {
    return rhs;
}

Statt nacheinander die Elemente mit swap zu vertauschen verwendet man den neuen :=:-Operator. Beziehungsweise die &&-Variante, die für das swappen mit der rhs-Instanz da ist, die ohnehin bald weggeworfen würde gestaltet sich die Implementierung besonders leicht: Nimm als lhs einfach rhs.

Dadurch ist hervorgehoben, dass es sich um eine sehr spezielle Funktion handelt, und kommt somit namenstechnisch dem operator= näher. Der Benutzer wird eher darauf gestoßen, dass er sie implementieren sollte.

Es gibt nich weitere kleine Ergänzungen für :=:, zum Beispiel für die eingebauten Typen.

Glaskugel

Hauptsächlich handelt es sich um eine Sytnax-Erweiterung mit nur wenig funktionalen Ergänzungen. Meine Vorraussage ist, dass dieses Feature es nicht in den Standard schafft. Wenn es nur um Syntax geht, war das Kommitee eher zurückhaltend.

posted at 10:20  ·   ·  assign  C++  C++1y  copy  operator  swap

Sep 09, 2013

Endorsing-Unsinn

Mal was ganz anderes. Wöchentlich bekomme ich von Linkedin Mails, wie folgt:

Torsten,

Congratulations! Your connection Xyz Abc has endorsed you for the following skills and expertise:

Perl

Zugegeben, ich habe mein Profil vor ca 11 Jahren dort erstellt, als ich aus der Bioinformatik (Perl-lastig) heraus einen neuen Job suchte, aber wir wissen ja: Das Internet vergisst nie.

Ja, Perl habe ich mal gemacht, ja ich kann Perl-Programme lesen und zur Not auch erweitern, ja, darum steht da * Perl*. Nicht dass ich da besonders storlz drauf bin, auch egal ob ich jemals wieder Perl anfassen möchte oder nicht – es steht da.

Schade nur, dass von meinen Kollegen damals niemand wusste, dass ich seit jeher viel lieber (und besser) C++ mache, aber wahrscheinlich mehr “hinter den Kulissen”. Und die noch älteren Kollegen wissen nicht, dass ich vor 12 Jahren zu Python gewechselt bin – in gewissen Kreisen scheinbar nicht so verbreitet, seltsamerweise ;-).

Sagen wir ich habe o.B.d.A. 100 nette Kollegen und Ex-Kollegen.... dann kann ich jetzt zu 90% Perl, zu 50% Python und zu 5% diese extrem esoterischen Sprache… uh, wie heißt die noch? Achja… C++.

Hach, das Internet funktioniert einfach nicht.

/schmunzel
/rant.

Aug 16, 2012

GCC C++ Coding Guidelines als Guideline?

Der GCC hat nun eine C++-Implementierung für alle drei Compilerphasen.

Da lohnt es sich mal einen Blick darauf zu werfen, was Gnu von C++ alles nutzen möchte. C++ ist nunmal eine große Sprache und man muss sich in einem Projekt entscheiden, was man davon nutzen will.

Ich fasse mal ganz knapp die Wiki-Seite zusammen. Diese Konventionen haben aber noch recht wenig mit C++11 zu tun, weil “der aktuelle GCC sich immer mit der vorigen (stabilen) Version bauen lassen muss”.

Allgemeine und Dialekt-Regeln

  • Der Code muss sowohl C++03 als auch C++11 konform sein.
    Das liegt natürlich daran, dass eine vorige Version den neuen Compiler übersetzen können muss. In einem eigenen Projekt müsste man diese Regel nicht unbedingt verwenden.
  • Kein RTTI und somit kein dynamic_cast und keine dynamischen Exceptionsspezifikationen throw(...)
  • und (tadaaa:) keine Exceptions! Warum, das wird auch erwähnt: Im Moment ist der Großteil des GCC-Codes nicht Exception-Safe. Dass es gut wäre, den Code auf RAII umzustellen, wird aber auch erwähnt. Das ist jedoch extrem viel Aufwand. Dann wären Exceptions natürlich nötig.
  • Statt auto_ptr nur tr1::shared_ptr, denn ersterer sei defekt. Wenn man sich selbst für C++11 entscheidet, dann kann man wohl auch den C++11 shared_ptr verwenden.

Welche Features?

  • Keine neuen Templates einführen.
  • Keine usings und keine Nested Namespaces.
  • Statt Konversions-Operatoren immer explizite Methoden verwenden. Also statt operator int() besser int getInt().
  • Konstruktoren mit einem Argument sollten immer explicit sein. Auch dies vermeidet überraschende Konvertierungen.
  • Keine Mehrfachvererbung.
  • Copy- und Assignment-Operatoren sollten vermieden werden, sind aber o.k. für pure Werteklassen. Der Grund ist, dass der Compiler selbst brauchbare Versionen generiert, wenn die Klasse keine Pointer enthält. Und wenn sie das tut, dann soll man sicherheitshalber Copy und Assign verbieten. Das führt langfristig dazu, dass Entwickler weniger Raw-Pointer verwenden werden.

C++ Konventionen

  • class und struct sind konzeptionell zu unterscheiden.
  • Konstruktoren sollen alle Datenfelder in ihrer Initialisierungsliste setzen.
  • Alle Datenfelder von Klassen sind private.
  • Namen von Datenfeldern sollten mit einem “_” enden, also zum Beispiel data_.
  • Methoden sollten diese Datefelder dann mit this verwenden, also zum Beispiel this->data_ (oder natürlich dem Getter).

Das kann als Anregung für eigene Konventionen dienen.

Mai 29, 2012

C++ Networking als Teil des Standards

Natürlich wird es nicht ein paar Jahre dauern bis aus der Ankündigung, dass eine Arbeitsgruppe die Arbeit aufnimmt letztlich ein Standard wird, aber es geht vorwärts.

Im Oktober will die Study Group 4 Ideen zur Standardisierung zur Netzwerkprogrammierung vorstellen: IPv4 und IPv6-Adressen, Adressen auflösen, Ports und URIs, Datagramm- und Netzwerk-Streams sowie ein erstes SSL-Interface. Auf dieser Basis sollen dann später höhere Protokolle wie HTTP und FTP aufgesetzt werden.

Bis dahin bittet die Gruppe um die Einreichung von Vorschlägen für ein Socket Interface, das zum Standard werden könnte.

posted at 15:01  ·   ·  C++  c++11  ft  http  ipv4  ipv6  networking  sg4

Apr 03, 2012

Schrödinger programmiert C++

…Das etwas andere Lehrbuch (Galileo Computing)

Ich kann dieses Buch sehr empfehlen.

Buchbild

Es liefert einen guten Einstieg in C++.

Es ist locker geschrieben, bleibt aber dennoch bei den Fakten. Es bringt die C++igen Konzepte durchaus herüber, ist jedoch nicht dogmatisch. Der Leser wird auch nützliche C’ismen erlernen, aber meist mit dem Hinweis, dass es sich hierbei um “gutes altes Tse” handelt. Am anderen Ende der Skala kommt auch C++11 nicht zu kurz.

Das Buch ist auch noch aufwendig farbig produziert, ein Augenschmaus und Schmuck auf dem Schreibtisch.

Externe Links

Apr 03, 2012

Mit C++11 hört C++ nicht auf -- unbounded-precision integer types

Mit der Verabschiedung von C++11 gibt sich die Gemeinde natürlich nicht zufrieden. Auch C++ wird sich natürlich noch weiter entwickeln. Einige Dinge wurden aus C++0x herausgelassen, damit “0x” es letztendlich wenigstens “11” werden konnte. Man wird aber sicher alte Bekannte irgendwann wiedersehen.

Dazu gehören zuvorderst natürlich die Concepts, aber viele erwarten auch Asynchrone IO oder Filesystem- und Dateinamenbehandlung – oder irgendetwas aus dem Umfeld von Boost.

C++1y, TR, oder Papierkorb

Was in den Standard Einzug halten wird, ob wann, oder ob es einen Nachtrag oder Technical Report mit Erweiterungen geben wird, steht natürlich in den Sternen. Nein, nicht in den Sternen – man kann sich an den Diskussionen ja beteiligen und vielleicht deren Ausgang ein wenig beeinflussen. Für den Interessierten lohnt es sich also, die Augen offen zu halten…

Es tut sich viel, aber ich greife mal exemplarisch etwas heraus…

integer und unsigned_integer

Neu in der Diskussion ist eine kleine aber feine Erweiterung für Ganzzahlen mit unbegrenzter Genauigkeit. “Unbegrenzt” meint hier dynamisch unbegrenzt, im Vergleich zu statisch unbegrenzt. Es wäre ein wenig leichter, zum Beispiel einen Zahltypen zu definieren, bei dem man bei der Initialisierung eine maximale Länge angeben muss – überschreitet man die, gäbe es einen Überlauf. Statt aber die Klasse integer auf einer Art festem Array zu basieren, schwebt den Autoren von N3375 konzeptionell eher ein vector vor. So kann kein Überlauf mehr passieren, die Größe passt sich automatisch an – bis der Speicher ausgeht…

Da gibt es natürlich schon jede Menge Bibliotheken (zum Beispiel die vielseitige Gnu GMP) , aber die wenigsten schmecken nach C++11.

Mit den bestehenden Zahlentypen wird integer natürlich interagieren. Die arithmetischen wie +, * und Verwandte werden mit sich selbst und int-Varianten unterstützt.

    integer i = 30000;
    integer j = 1000 * i;

Allerdings geschieht dies im aktuellen Vorschlag nicht durch Überladung von operator+() etc mit allem möglichen int -Varianten, sondern durch die implizite Umwandlung von int in integer.

Der aktuelle Vorschlag bietet:

  • arithmetische Operationen +, -, *, /, %, – und ++
  • eine Funktion div(), die gleichzeitig das ganzzahlige Ergebnis und den Rest zurückgibt,
  • Bitoperationen |, &, \^, \<\<, >>, \<\<=, >>=, |=, &= und\^=.
  • Vergleiche ==, \<, >, !=, \<= und >=
  • Quadrat, Quadratwurzel sowie `sqrtrem()` um gleichzeitig Quadratwurzel und Rest zu erhalten
  • Exponenzieren mit pow(), bzw. powmod() – letzterest ist ein häufiger Anwendungsfall, der eine platzsparendes pow() gefolt von einer Modulo-Operation ist. Den GGT und KGV kann man mit gcd() und lcm() berechnen.
  • Eine beliebig lange Zufallszahl kann man erstellen,
  • Stream-Ein- und Ausgabe wird unterstützt; mit `to_string(base=10)` ist ebenfalls praktisch.

Noch Offen

Chancen sich einzubringen hat man vielleicht insbesondere bei den noch offenen Punkten des aktuellen Vorschlags:

  • Wie soll die Speicherverwaltung duch den Benutzer beeinflußt werden können? Wie sollen Allocators Eingang finden?
  • Was soll passieren bei einem Unterlauf, zum Beispiel bei
    • unsigned_integer neg = -99;?
    • unsigned_integer five = 5 und dann unsigned_integer result = five - 100?
    • unsigned_integer five = 5; unsigned_integer ten = 10, dann five - ten?
  • Wie ist mit Präzisionsverlust umzugehen, wenn ein sehr großer integer in einen int umgewandelt werden soll?

Wer hat Vorschläge?

Links

N3375 - Proposal for Unbounded-Precision Integer Types