Wenn Sie zu Beginn des Stücks sagen, dass ein C++-Code an der Wand hängt, muss er am Ende unbedingt Ihnen ins Knie schießen.
Bjarne Stroustrup
Vom 31. Oktober bis 1. November fand in St. Petersburg die Konferenz C++ Russland Piter statt – eine der größten Programmierkonferenzen in Russland, organisiert von der JUG Ru Group. Zu den eingeladenen Referenten gehören Mitglieder des C++-Standardisierungsgremiums, Redner von CppCon, Autoren von Büchern des O’Reilly-Verlags sowie Maintainer von Projekten wie LLVM, libc++ und Boost. Die Konferenz richtet sich an erfahrene C++-Entwickler, die ihre Expertise vertiefen und sich im persönlichen Austausch über Erfahrungen austauschen möchten. Studierenden, Doktoranden und Hochschullehrern werden sehr attraktive Rabatte angeboten.
Die Moskauer Ausgabe der Konferenz kann bereits im April nächsten Jahres besucht werden, aber inzwischen werden unsere Studierenden berichten, was sie auf der vergangenen Veranstaltung Interessantes gelernt haben.

Foto aus
Alconost ist professionell tätig
An diesem Beitrag arbeiteten zwei Studierende der HSE – Sankt Petersburg:
- Lisa Vasilenko – Bachelorstudentin im 4. Jahr, die im Bereich „Programmiersprachen“ im Rahmen des Programms „Angewandte Mathematik und Informatik“ studiert. Nachdem sie im ersten Studienjahr mit C++ vertraut wurde, sammelte sie anschließend Erfahrung durch Praktika in der Industrie. Ihre Begeisterung für Programmiersprachen im Allgemeinen und funktionale Programmierung im Besonderen beeinflusste ihre Auswahl der Vorträge auf der Konferenz.
- Danja Smirnov – Masterstudent im 1. Jahr im Studiengang „Programmierung und Datenanalyse“. Bereits in der Schule löste er Wettbewerbsaufgaben mit C++, und im weiteren Verlauf kam es so, dass die Sprache ständig in seiner Studienaktivität auftauchte und schließlich zu seiner Hauptarbeitssprache wurde. An der Konferenz nahm er teil, um sein Wissen zu vertiefen und neue Möglichkeiten zu erfahren.
In dem Newsletter teilt die Fakultätsleitung häufig Informationen über bildungsbezogene Veranstaltungen, die mit unserem Fachgebiet zu tun haben. Im September sahen wir die Informationen zur C++ Russland und beschlossen, uns als Zuhörer zu registrieren. Dies ist unsere erste Erfahrung in der Teilnahme an solchen Konferenzen.
Die Struktur der Konferenz
Vorträge
Innerhalb von zwei Tagen lasen die Experten 30 Vorträge zu vielen heißen Themen: witzige Anwendungen der Sprachfeatures zur Lösung praktischer Aufgaben, bevorstehende Aktualisierungen der Sprache im Zusammenhang mit dem neuen Standard, Kompromisse beim Design von C++ und Vorsichtsmaßnahmen im Umgang mit deren Folgen, Beispiele interessanter Projektarchitektur sowie einige interne Details der Sprachinfrastruktur. Gleichzeitig fanden 3 Präsentationen statt, meistens zwei auf Russisch und eine auf Englisch.
Diskussionszonen
Nach den Vorträgen wurden alle unbeantworteten Fragen und unvollendeten Diskussionen in speziell ausgewiesene Kommunikationszonen mit den Referenten verlegt, die mit Whiteboards ausgestattet waren. Ein guter Weg, die Pausen zwischen den Vorträgen mit angenehmen Gesprächen zu überbrücken.
Lightning Talks und informelle Diskussionen
Wenn Sie Lust auf einen kurzen Vortrag haben, können Sie sich an das Whiteboard für den abendlichen Lightning Talk anmelden und fünf Minuten Zeit bekommen, um über irgendetwas zum Thema der Konferenz zu berichten. Zum Beispiel eine schnelle Einführung in Sanitizers für C++ (war für einige neu) oder die Geschichte eines Bugs in der Sinuswellen-Generierung, den man nur hören, aber nicht sehen kann.
Ein anderes Format — ein Podiumsdiskussion „Mit dem Komitee über die Seele“. Auf der Bühne stehen einige Mitglieder des Standardisierungskomitees, auf dem Projektor ein Kamin (offiziell zur Schaffung einer gemütlichen Atmosphäre, aber der Grund „weil ALLES IN FLAMMEN STEHT“ scheint amüsanter zu sein), Fragen zum Standard und zur allgemeinen Sichtweise von C++, ohne hitzige technische Diskussionen und Streitigkeiten. Es stellte sich heraus, dass auch im Komitee lebende Menschen sitzen, die sich in manchen Punkten nicht ganz sicher sind oder etwas nicht wissen.
Für Streitliebhaber gab es die dritte Veranstaltung — eine BOF-Session „Go gegen C++“. Wir nehmen einen Go-Liebhaber und einen C++-Liebhaber, bevor die Session beginnt, bereiten sie gemeinsam 100500 Folien zu Themen vor (wie z.B. Probleme mit Paketen in C++ oder das Fehlen von Generika in Go) und dann diskutieren sie lebhaft miteinander und mit dem Publikum, während das Publikum versucht, sofort zwei Perspektiven zu verstehen. Wenn ein Streit ohne Grund beginnt, greift der Moderator ein und schlichtet die Parteien. Dieses Format zieht sich: mehrere Stunden nach Beginn war erst die Hälfte der Folien behandelt. Das Ende musste stark beschleunigt werden.
Partnerstände
In den Foyers waren die Partner der Konferenz vertreten – an den Ständen wurden aktuelle Projekte vorgestellt, Praktika und Stellenangebote angeboten, Quizze und kleinere Wettbewerbe durchgeführt und angenehme Preise verlost. Einige Unternehmen boten sogar an, die ersten Schritte in Bewerbungsgesprächen zu machen, was für diejenigen, die nicht nur zum Zuhören gekommen sind, hilfreich sein kann.
Technische Einzelheiten der Vorträge
Wir haben an beiden Tagen den Vorträgen gelauscht. Manchmal war es schwierig, einen Vortrag aus den parallel laufenden zu wählen – wir hatten uns darauf geeinigt, uns aufzuteilen und in den Pausen unser Wissen auszutauschen. Und selbst so scheint es, dass wir viel verpasst haben. Hier möchten wir über den Inhalt einiger Vorträge berichten, die uns am interessantesten erschienen.
Ausnahmen in C++ durch die Linse der Compiler-Optimierungen, Roman Rusyaev

Folie aus
Wie der Titel bereits andeutet, hat Roman die Arbeit mit Ausnahmen am Beispiel von LLVM behandelt. Selbst für diejenigen, die Clang nicht verwenden, kann der Vortrag dennoch einen gewissen Einblick geben, wie der Code potenziell optimiert werden kann. Das liegt daran, dass Compiler-Entwickler und entsprechende Standardbibliotheken miteinander kommunizieren und viele erfolgreiche Lösungen sich überschneiden können.
Um eine Ausnahme zu verarbeiten, sind viele Schritte erforderlich: den Handlingscode aufzurufen (falls vorhanden) oder Ressourcen auf der aktuellen Ebene freizugeben und den Stack höher zu entwirren. Das führt dazu, dass der Compiler zusätzliche Anweisungen für potenziell auslösende Aufrufe hinzufügt. Daher kann es passieren, dass auch wenn die Ausnahme tatsächlich nicht ausgelöst wird, das Programm trotzdem unnötige Aktionen ausführt. Um die Overheadkosten zu senken, gibt es in LLVM mehrere Heuristiken zur Bestimmung von Situationen, in denen der Handlingscode nicht hinzugefügt werden muss oder die Anzahl der "überflüssigen" Anweisungen reduziert werden kann.
Der Vortragende betrachtet etwa ein Dutzend dieser Heuristiken und zeigt sowohl die Situationen, in denen sie helfen, die Ausführung des Programms zu beschleunigen, als auch die, in denen diese Methoden nicht anwendbar sind.
So kommt Roman Rusyaev zu dem Schluss, dass Code, der Ausnahmen verarbeitet, längst nicht immer mit null Overhead-Kosten ausgeführt werden kann, und gibt folgende Ratschläge:
- Bei der Entwicklung von Bibliotheken sollte man im Prinzip auf Ausnahmen verzichten;
- Falls Ausnahmen doch nötig sind, sollte man überall, wenn möglich, Modifikatoren wie noexcept (und const) hinzufügen, damit der Compiler so viel wie möglich optimieren kann.
Insgesamt bestätigte der Referent die Meinung, dass Ausnahmen am besten minimiert oder ganz vermieden werden sollten.
Die Folien des Vortrags sind unter folgendem Link verfügbar:
Generatoren, Koroutinen und andere süße Gehirn-Entwirrungen, Adi Shavit

Folie aus
Einer von vielen Vorträgen auf dieser Konferenz, die sich mit Neuerungen in C++20 beschäftigen, bleibt nicht nur wegen der farbenfrohen Präsentation im Gedächtnis, sondern auch wegen der klaren Benennung bestehender Probleme bei der Logik der Verarbeitung von Sammlungen (for-Schleife, Callback-Mechanismen).
Adi Shavit hebt Folgendes hervor: Die aktuellen Methoden durchlaufen die Sammlung vollständig und bieten dabei keinen Zugriff auf einen internen Zwischenzustand (oder geben diesen im Fall von Callbacks, aber mit vielen unangenehmen Nebeneffekten, wie dem Callback-Hell, preis). Es gibt zwar Iteratoren, aber auch diese sind nicht ideal: Es gibt keinen gemeinsamen Einstieg und Ausstieg (begin → end gegen rbegin → rend usw.), und es ist unklar, wie oft wir eigentlich iterieren werden? Diese Probleme werden ab C++20 gelöst!
Die erste Möglichkeit: Ranges. Durch eine Hülle über Iteratoren erhalten wir eine einheitliche Schnittstelle für den Beginn und das Ende der Iteration sowie die Möglichkeit zur Komposition. Das ermöglicht es, vollständige Datenverarbeitungs-Pipelines leicht zu erstellen. Doch nicht alles läuft glatt: Ein Teil der Logik der Berechnungen befindet sich in der Implementierung eines spezifischen Iterators, was den Code für die Wahrnehmung und das Debugging kompliziert machen kann.

Folie aus
Nun, für diesen Fall wurden in C++20 Koroutinen hinzugefügt (Funktionen, deren Verhalten dem von Generatoren in Python ähnelt): Die Ausführung kann verzögert werden, indem ein aktueller Wert zurückgegeben wird, während der Zwischenzustand erhalten bleibt. So erreichen wir nicht nur die Verarbeitung von Daten, sobald sie verfügbar sind, sondern kapseln auch die gesamte Logik innerhalb einer spezifischen Koroutine ein.
Aber es gibt einen Wermutstropfen: Derzeit werden sie nur teilweise von den vorhandenen Compilern unterstützt, und sie sind auch nicht so sorgfältig implementiert, wie man es sich wünschen würde: Zum Beispiel sollte man in Koroutinen momentan keine Referenzen und temporären Objekte verwenden. Außerdem gibt es einige Einschränkungen, was als Koroutinen gelten kann; constexpr-Funktionen, Konstruktoren/Dekonstruktoren sowie main gehören nicht zu dieser Liste.
Auf diese Weise lösen Koroutinen einen erheblichen Teil der Probleme in Bezug auf die Einfachheit der Datenverarbeitung, aber die aktuellen Implementierungen bedürfen noch einer Überarbeitung.
Materialien:
- Folien von C++ Russia —
C++ Tricks aus Yandex.Taxi, Anton Poluchin
In meiner beruflichen Tätigkeit muss ich manchmal rein unterstützende Dinge implementieren: eine Schnittstelle zwischen einem internen Interface und der API einer Bibliothek, Logging oder Parsing. Dabei besteht normalerweise keine Notwendigkeit für zusätzliche Optimierungen. Aber was, wenn diese Komponenten in einem der beliebtesten Dienste im Runet verwendet werden? In einem solchen Fall müssen Terabytes pro Stunde allein an Logs verarbeitet werden! Dann zählt jede Millisekunde, und deshalb muss man auf verschiedene Tricks zurückgreifen — und genau darüber hat Anton Poluchin gesprochen.
Das wohl interessanteste Beispiel war die Implementierung des pointer-to-implementation (pimpl) Patterns.
#include <third_party/json.hpp> //PROBLEMS!
struct Value {
Value() = default;
Value(Value&& other) = default;
Value& operator=(Value&& other) = default;
~Value() = default;
std::size_t Size() const { return data_.size(); }
private:
third_party::Json data_;
};In diesem Beispiel möchte ich zunächst die Header-Dateien externer Bibliotheken loswerden — das beschleunigt die Kompilierung und schützt vor möglichen Namenskonflikten und ähnlichen Fehlern.
Gut, wir haben #include in die .cpp-Datei verschoben: Wir benötigen eine Forward-Deklaration der umschlossenen API sowie std::unique_ptr. Jetzt haben wir dynamische Allokationen und andere unangenehme Dinge wie verstreute Daten im Heap und reduzierte Garantien. Mit all dem kann std::aligned_storage helfen.
struct Value {
// ...
private:
using JsonNative = third_party::Json;
const JsonNative* Ptr() const noexcept;
JsonNative* Ptr() noexcept;
constexpr std::size_t kImplSize = 32;
constexpr std::size_t kImplAlign = 8;
std::aligned_storage_t data_;
};Das einzige Problem: Für jede Wrapper müssen Größe und Ausrichtung angegeben werden — machen wir unser pimpl templated mit den Parametern , verwenden wir einige beliebige Werte und fügen eine Überprüfung im Destruktor hinzu, um sicherzustellen, dass wir alles richtig erraten haben:
~FastPimpl() noexcept {
validate();
Ptr()->~T();
}
template
static void validate() noexcept {
static_assert(
Size == ActualSize,
"Size und sizeof(T) stimmen nicht überein"
);
static_assert(
Alignment == ActualAlignment,
"Ausrichtung und alignof(T) stimmen nicht überein"
);
}Da der Destruktor T bereits definiert ist, wird dieser Code korrekt analysiert und gibt in der Kompilierungsphase die erforderlichen Werte für Größe und Ausrichtung aus, die eingetragen werden müssen. So vermeiden wir durch einen zusätzlichen Kompilierungsdurchlauf die dynamische Zuweisung der umschlossenen Klassen, verstecken die API in einer .cpp-Datei mit der Implementierung und erhalten zudem eine für den Prozessor besser zwischenspeicherbare Struktur.
Logging und Parsing schienen weniger beeindruckend zu sein, weshalb sie in diesem Überblick nicht erwähnt werden.
Die Folien des Vortrags sind unter folgendem Link verfügbar:
Moderne Techniken, um Ihren Code DRY zu halten, Björn Fahller
In diesem Vortrag zeigt Björn Fahller mehrere verschiedene Möglichkeiten, gegen das stilistische Manko wiederholter Bedingungsprüfungen vorzugehen:
assert(a == IDLE || a == CONNECTED || a == DISCONNECTED);Kennen Sie das? Mit Hilfe einiger leistungsstarker C++-Techniken, die in den letzten Standards eingeführt wurden, lässt sich dieselbe Funktionalität elegant umsetzen, ohne auch nur ein wenig an Leistung zu verlieren. Vergleichen Sie:
assert(a == any_of(IDLE, CONNECTED, DISCONNECTED));Um eine unbestimmte Anzahl von Prüfungen effektiv zu handhaben, bietet es sich an, variadic templates und fold expressions zu verwenden. Nehmen wir an, wir möchten die Gleichheit mehrerer Variablen mit einem enum-Wert des Typs state_type prüfen. Das Erste, was in den Sinn kommt, ist das Schreiben einer Hilfsfunktion is_any_of:
enum state_type { IDLE, CONNECTED, DISCONNECTED };
template
bool is_any_of(state_type s, const Ts& ... ts) {
return ((s == ts) || ...);
}
Ein solches Zwischenergebnis führt zu Enttäuschung. Bisher bleibt der Code nicht leserlicher:
assert(is_any_of(state, IDLE, DISCONNECTING, DISCONNECTED)); Ein wenig helfen werden non-type template parameters. Damit bringen wir die aufgezählten Elemente des enums in eine Liste von Template-Parametern:
template
bool is_any_of(state_type t) {
return ((t == states) | ...);
}
assert(is_any_of(state)); Mit der Verwendung von auto in einem nicht typisierten Template-Parameter (C++17) generalisiert sich der Ansatz einfach auf Vergleiche nicht nur mit den Elementen von state_type, sondern auch mit primitiven Typen, die als non-type template parameters verwendet werden können:
template
bool is_any_of(const T& t) {
return ((t == alternatives) | ...);
}Durch solche schrittweisen Verbesserungen wird die gewünschte flüssige Syntax für Überprüfungen erreicht:
template
struct any_of : private std::tuple {
// lasst uns faulenzen und die Konstruktoren von tuple erben
using std::tuple::tuple;
template
bool operator ==(const T& t) const {
return std::apply(
[&t](const auto& ... ts) {
return ((ts == t) || ...);
},
static_cast<const std::tuple&>(*this));
}
};
template
any_of(Ts ...) -> any_of;
assert(any_of(IDLE, DISCONNECTING, DISCONNECTED) == state);
In diesem Beispiel dient der Deduktionsleitfaden dazu, dem Compiler die gewünschten Template-Parameter der Struktur anhand der Typen der Konstruktorargumente zu vermitteln.
Es wird noch spannender. Björn zeigt, wie man den entstandenen Code für Vergleichsoperatoren über == hinaus verallgemeinert und dann auch für beliebige Operationen. Nebenbei werden anhand der Verwendung solche Funktionen wie das Attribut no_unique_address (C++20) und Template-Parameter in Lambda-Funktionen (C++20) erklärt. (Ja, jetzt ist die Syntax von Lambdas noch leichter zu merken – es sind vier aufeinanderfolgende Paarungen von Klammern aller Art.) Die finale Lösung mit der Verwendung von Funktionen als Zerteilungen des Konstruktors erwärmt mir persönlich das Herz, ganz zu schweigen von der Darstellung eines Tupels in besten Traditionen der Lambda-Kalküle.
Am Ende vergessen wir nicht, den Feinschliff anzuwenden:
- Erinnern wir uns, dass Lambdas kostenlos constexpr sind;
- Wir fügen Perfect Forwarding hinzu und betrachten seine hässliche Syntax in Bezug auf Parameterpakete in der Lambda-Bindung;
- Wir geben dem Compiler mehr Möglichkeiten zur Optimierung mit conditional noexcept;
- Wir sorgen für eine klarere Fehlerausgabe in Templates durch explizite Rückgabewerte von Lambdas. Das zwingt den Compiler dazu, mehr Prüfungen vor dem eigentlichen Aufruf der Template-Funktion durchzuführen – in der Phase der Typüberprüfung.
Für weitere Details wenden Sie sich an die Materialien der Vorlesung:
- Folien des Vortrags:
Unsere Eindrücke
Unser erstes Mitwirken an C++ Russia bleibt uns durch seine Fülle in Erinnerung. Der Eindruck von C++ Russia ist der eines herzlichen Events, bei dem die Grenze zwischen Lernen und lebendigem Austausch kaum spürbar ist. Alles, von der Stimmung der Referenten bis zu den Wettbewerben der Veranstaltungspartner, fördert lebhafte Diskussionen. Der inhaltliche Teil der Konferenz, bestehend aus Vorträgen, umfasst ein recht breites Themenspektrum, einschließlich Neuerungen in C++, praktischen Beispielen großer Projekte und ideologischen Architekturüberlegungen. Es wäre ungerecht, auch die soziale Komponente der Veranstaltung zu vernachlässigen, die dazu beiträgt, Sprachbarrieren nicht nur in Bezug auf C++ zu überwinden.
Wir danken den Organisatoren der Konferenz für die Möglichkeit, an solch einem Event teilzunehmen!
Den Beitrag der Organisatoren über die Vergangenheit, Gegenwart und Zukunft von C++ Russia haben Sie vielleicht gesehen .
Vielen Dank fürs Lesen, und wir hoffen, dass unsere Zusammenfassung der Ereignisse hilfreich war!
Quelle: habr.com
