Scott Meyers´ Effektive C++ Richtlinien erfolgreich anwenden

Scott Meyers´ Effektive C++ Richtlinien erfolgreich anwenden

Die Ursprünge von C++

Die Programmiersprache C++ hat seit ihrer Einführung im Jahr 1985 eine lange Geschichte im Bereich der Softwareentwicklung.

C++ wurde ursprünglich von Bjarne Stroustrup, einem dänischen Informatiker, als Erweiterung der Programmiersprache C konzipiert.

Zu diesem Zeitpunkt war C bereits sehr weit verbreitet, insbesondere in der Embedded-Software-Entwicklung.  

Die Programmiersprache C wurde von vielen im Bereich der Embedded-Software aufgrund ihrer „Low-Level“-Verbindung bevorzugt.

Grund hierfür war das zugrunde liegende System, die Leistung und Effizienz sowie die Möglichkeit des Programmierers, fast alles zu tun, was er wollte.

C++ hat diese Programmierfreiheit erweitert, mit noch größeren Optionen und Möglichkeiten, Dinge effizienter oder schneller zu erledigen.

Dadurch wurde es jedoch auch komplexer für den Programmierer. Fragen wie: “Auf welche „Weise“ kann ich dies oder jenes am besten tun?“, „Wie verstehen das die Menschen um mich herum?“ und „Wie sieht der Zusammenhang aus?” standen im Raum.

Man nahm an, dass, wenn C das „scharfe Messer“ der Programmiersprachen ist – ein äußerst nützliches und mächtiges Werkzeug, aber nicht ohne Risiken für den Benutzer – dann kann C++ als die abgesägte Schrotflinte betrachtet werden. Gehen Sie daher vorsichtig vor 😉

Ein Pionier und Meister auf seinem Gebiet – Scott Meyers

Scott Meyers ist ein amerikanischer Autor und Softwareberater, der sich auf die Computerprogrammiersprache C++ spezialisiert hat.

Er ist bekannt für seine Buchreihe „Effective C++“. 

Sein Hauptprinzip ist einfach und tiefgründig zugleich: Es genügt nicht, nur syntaktisch korrekt zu sein – man muss auch effektiv handeln.

Dieser Unterschied ist von größter Bedeutung.

Während viele Ressourcen das „Wie“ der C++-Codierung lehren, konzentriert sich Meyers auf das „Warum“: 

• Warum sollten bestimmte Strukturen bevorzugt werden? 

• Warum führen bestimmte Ansätze zu besserem und effizienterem Code? 

• Warum können scheinbar unbedeutende Entscheidungen zu erheblichen Fehlern führen? 

„Effective C++“ ist mehr als nur ein Buch – es steht für eine ganze Bewegung.

Scott Meyers‘ Ziel ist es, die Herangehensweise der Entwickler an C++ zu revolutionieren.

Sein Buch ist nicht nur ein Ratgeber, sondern ein intensives Seminar über die Kunst des Programmierens.

Die verschiedenen Ausgaben dieser Reihe bieten eine Fülle von Tipps und Richtlinien, die darauf abzielen, die Codequalität, Wartbarkeit und Leistung zu verbessern. 

Anmerkung: Die folgenden Informationen über Scott Meyers‘ effektives C++ sind sein geistiges Eigentum und haben nichts mit Emenda zu tun. 

Das erste Prinzip von Scott Meyers

Unabhängig von Ihren individuellen Programmierkenntnissen müssen Sie sich zunächst mit der Funktionsweise von C++ vertraut machen. 

1. C++ ist eine leistungsstarke Sprache mit einem beeindruckenden Funktionsumfang. Es sollte als eine Föderation verschiedener Sprachen gesehen werden.

Die Richtlinien für eine erfolgreiche C++-Programmierung variieren je nachdem, welchen Aspekt von C++ Sie verwenden.

2. Der Compiler ist immer dem Präprozessor vorzuziehen.

Das Dilemma besteht darin, #define so zu behandeln, als wäre es kein integraler Bestandteil der Sprache.

Entscheiden Sie sich daher für einfache Konstanten für const-Objekte und Enumerationen anstelle von #define.

Bevorzugen Sie für makroähnliche Funktionen Inlinefunktionen gegenüber #define. 

Definiere

Definiere nicht

3. Verwenden Sie const so oft wie möglich. Das Keyword const zeichnet sich durch seine bemerkenswerte Flexibilität aus.

• Außerhalb von Klassen dient es dazu, globale Konstanten oder Konstanten auf Namespace-Ebene sowie Objekte zu definieren, die auf Datei-, Funktions- oder Blockebene als „statisch“ deklariert sind. 

• Innerhalb der Klassen kann Const sowohl für statische als auch für nicht statische Datenmember verwendet werden. 

• Bei Pointern kann bestimmt werden, ob der Pointer selbst, die Daten, auf die er zeigt, beides oder keines von beiden Const ist. 

• Deklarieren als Const hilft Compilern, Fehler bei der Verwendung aufzudecken. Const kann auf Objekte mit beliebigem Gültigkeitsbereich, Funktionsparametern und Rückgabetypen, sowie auf ganze Memberfunktionen angewendet werden. 

• Während Compiler bitweise Konstanz erzwingen, sollten Sie bei der Entwicklung die konzeptionelle Konstanz berücksichtigen. 

• Wenn Const und Nicht-Const Memberfunktionen im Wesentlichen die gleiche Implementierung verwenden, kann der Aufruf der Const Version in der Nicht-Const Version Code-Duplizierung vermeiden. 

4. Stellen Sie sicher, dass Objekte vor ihrer Verwendung initialisiert werden.

• Der Zugriff auf nicht initialisierte Werte führt zu unvorhersehbarem Verhalten. 

• Initialisieren Sie Objekte integrierter Typen manuell, da sie in C++ nicht immer automatisch initialisiert werden. 

• Verwechseln Sie die Zuweisung nicht mit der Initialisierung. 

• Bevorzugen Sie die Initialisierungsliste für Membervariablen im Konstruktor anstelle von Zuweisungen im Konstruktortext. Listen Sie die Datenmember in der Initialisierungsliste in der Reihenfolge auf, in der sie in der Klasse definiert sind. 

• Um Probleme mit der Initialisierungsreihenfolge zwischen verschiedenen Übersetzungseinheiten zu vermeiden, ersetzen Sie nicht-lokale statische Objekte durch lokale statische Objekte. 

Das 2. Prinzip von Scott Meyers

Fast jede Klasse, die Sie erstellen, verfügt über einen oder mehrere Konstruktoren, einen Destruktor und einen Kopierzuweisungsoperator.

Fehler in diesen Funktionen können sich auf die gesamte Klasse auswirken. 

1. Seien Sie sich der Funktionen bewusst, die C++ im Hintergrund ausführt.

Dieser Aspekt unterstreicht ein wiederkehrendes Thema in Scott Meyers‘ Schreiben: die Gefahren des Unsichtbaren und Unaussprechlichen.

Durch die Hervorhebung automatisch generierter Konstruktoren und Zuweisungsoperatoren sensibilisiert er Entwickler für potenzielle Fallstricke, die durch unbeabsichtigte Vernachlässigung entstehen können. 

• Compiler können den Standardkonstruktor, den Kopierkonstruktor, den Kopierzuweisungsoperator und den Destruktor einer Klasse ohne explizite Anweisungen generieren. 

• In C++ ist es nicht möglich, einen Verweis auf ein anderes Objekt umzuleiten. 

2. Verbieten Sie explizit die Verwendung von vom Compiler generierten Funktionen, die Sie ablehnen. 

Wenn Sie nicht möchten, dass Compiler standardmäßig bereitgestellte Funktionen zulassen, deklarieren sie diese Memberfunktionen als privat und stellen Sie keine Implementierungen für sie bereit.

Ein Ansatz, um dies zu erreichen, besteht darin, eine Basisklasse wie „uncopyable“ zu verwenden. 

3. In polymorphen Basisklassen sollten Destruktoren als virtuell deklariert werden.

• Eine Funktion, die einen Basisklassenzeiger zurückgibt, der auf ein neu generiertes Objekt einer abgeleiteten Klasse zeigt, kann verwendet werden, um einen Pointer auf ein Zeitobjekt zu übermitteln. 

• Polymorphe Basisklassen müssen virtuelle Destruktoren deklarieren. Wenn eine Klasse über virtuelle Funktionen verfügt, sollte sie auch über einen virtuellen Destruktor verfügen. 

• Klassen, die nicht als Basisklassen oder nicht für die polymorphe Verwendung vorgesehen sind, sollten keine virtuellen Destruktoren deklarieren. 

4. Vermeiden Sie das Auslösen von Ausnahmen bei Destruktoren.

• Obwohl C++ das Auslösen von Ausnahmen bei Destruktoren nicht verbietet, wird davon abgeraten. 

• Wenn Funktionen, die im Destruktor aufgerufen werden, möglicherweise Ausnahmen auslösen können, sollten diese Ausnahmen im Destruktor abgefangen und entweder unterdrückt oder das Programm beendet werden. 

• Wenn es erforderlich ist, dass Benutzer der Klasse auf Ausnahmen reagieren, die während eines Vorgangs ausgelöst werden, sollte die Klasse eine separate (nicht destruktive) Funktion zum Ausführen dieses Vorgangs bereitstellen. 

5. Vermeiden Sie es, virtuelle Funktionen während des Aufbaus oder der Zerstörung aufzurufen.

• Ein Objekt wird erst dann zu einem Objekt der abgeleiteten Klasse, wenn der Konstruktor dieser abgeleiteten Klasse ausgeführt wird. 

• Vermeiden Sie es, virtuelle Funktionen während der Erstellung oder Zerstörung aufzurufen, da solche Aufrufe niemals an eine Klasse weitergeleitet werden, die über den aktuell ausgeführten Konstruktor oder Destruktor hinausgeht. 

6. Stellen Sie sicher, dass Zuweisungsoperatoren einen Verweis auf *this zurückgeben.

7. Behandeln Sie die Selbstzuweisung in operator=.

• Stellen Sie sicher, dass operator= korrekt funktioniert, wenn ein Objekt sich selbst zugewiesen ist. Dazu gehören Methoden wie das Vergleichen von Adressen der Quell- und Zielobjekte, eine durchdachte Abfolge von Anweisungen sowie Kopier- und Austauschvorgänge. 

• Stellen Sie sicher, dass Funktionen, die sich auf mehrere Objekte auswirken, auch dann ordnungsgemäß funktionieren, wenn zwei oder mehr dieser Objekte identisch sind. 

8. Vollständiges Kopieren eines Objekts. 

• Stellen Sie bei der Implementierung von Kopierfunktionen sicher, dass alle Datenkomponenten eines Objekts und alle Aspekte seiner Basisklasse berücksichtigt werden. 

• Es sollte vermieden werden, eine Kopierfunktion durch die andere zu realisieren. Stattdessen ist es besser, die gemeinsam genutzte Funktionalität in einer separaten Funktion zu konsolidieren, welche von beiden aufgerufen wird. 

Scott Meyers‘ 3. Prinzip

Ressourcenmanagement

Eine Ressource bezieht sich auf etwas, das nach der Verwendung an das System zurückgegeben werden soll.

In C++ ist der am häufigsten verwendete Ressourcentyp dynamisch zugewiesener Speicher.

Eine unsachgemäße Zuordnung, Initialisierung oder Aufhebung der Zuweisung (Speicherverluste) kann zu Programmabstürzen, zu unerwartetem Verhalten oder Sicherheitsrisiken führen.

Weitere gängige Ressourcen sind Dateideskriptoren, Mutexsperren in Parallelität, Schriftarten und Zeichenwerkzeuge in grafischen Benutzeroberflächen (GUIs), Datenbankverbindungen, Netzwerkverbindungen und sogar Hardwaregeräte.

Unabhängig von der Art der Ressource ist es wichtig, sie nach Gebrauch freizugeben. 

1. Verwenden Sie Objekte für die Ressourcenverwaltung.

• Um das Risiko von Ressourcenlecks zu minimieren, setzen Sie auf das Prinzip “Ressourcenbeschaffung ist Initialisierung” (Resource Acquisition Is Initialisation, RAII) – dabei handelt es sich um Objekte, die während des Baus Ressourcen in ihren Konstruktoren anfordern und in ihren Destruktoren freigeben. 

• Zwei gängige RAII-Klassen sind: shared_ptr und auto_ptr

• In den meisten Fällen ist shared_ptr die bevorzugte Option, da das Kopierverhalten vorhersehbarer ist. Das Kopieren eines auto_ptr führt dazu, dass es zurückgesetzt wird. 

2. Überlegen Sie sorgfältig, wie Ressourcenverwaltungsklassen kopiert werden sollen.

• Beim Kopieren eines RAII-Objekts wird auch die verwaltete Ressource kopiert, was bedeutet, dass sich das Kopierverhalten der Ressource auf das Kopierverhalten des RAII-Objekts auswirkt. 

• In der Regel verhindern RAII-Klassen das Kopieren und führen eine Verweiszählung durch, es gibt jedoch auch andere Ansätze. 

3. Bereitstellen des Zugriffs auf Rohressourcen in Ressourcenverwaltungsklassen.

Dieser Zugriff kann entweder durch explizite oder implizite Konvertierung aktiviert werden.

Während die explizite Konvertierung im Allgemeinen sicherer ist, bietet die implizite Konvertierung den Benutzern mehr Komfort. 

4. Verwenden Sie die gleiche Syntax für new und delete in den entsprechenden Fällen.

Wenn Sie [] in einem new Befehl verwenden, müssen Sie auch [] in dem entsprechenden delete Befehl verwenden.

Wenn Sie [] in keinem new Befehl verwenden, sollten Sie es auch im entsprechenden delete Befehl vermeiden. 

5. Bewahren Sie Objekte, die mit new smart pointern erstellt wurden, in separaten Anweisungen auf.

Platzieren Sie neu erstellte Objekte in Smart Pointern in separaten Anweisungen.

Wenn Sie dies nicht tun, kann dies zu versteckten Ressourcenverlusten führen, sollten Ausnahmen ausgelöst werden. 

Scott Meyers‘ viertes Prinzip

Design und Definition

Softwaredesign – Konzepte, die Software anleiten um das gewünschte Verhalten zu erreichen – beginnt oft mit groben Skizzen.

Im Laufe der Zeit müssen sie jedoch präzise genug werden, um die Schaffung konkreter Schnittstellen zu ermöglichen.

Diese Schnittstellen werden dann in C++-Definitionen übersetzt. 

Die folgenden Aspekte betonen wichtige Überlegungen, warnen vor häufigen Fehlern und bieten Lösungen für Herausforderungen, mit denen Designer von Klassen, Funktionen und Vorlagen beim Interface-Design häufig konfrontiert sind. 

Entwerfen Sie Benutzeroberflächen so, dass sie intuitiv korrekt und schwer zu missbrauchen sind. Dies bildet die Grundlage für spezifischere Empfehlungen, die verschiedene Aspekte wie Präzision, Leistung, Kapselung, Wartbarkeit, Skalierbarkeit und Einhaltung von Standards abdecken. 

Behandeln Sie den Entwurf von Klassen als Typentwurf. 

Bevorzugen Sie die Verwendung von reference-to-Const gegenüber der Verwendung von Value

Vermeiden Sie es, einen Verweis zurückzugeben, wenn ein Objekt zurückgegeben werden soll. 

Definieren Sie Datenmember als privat. 

Bevorzugen Sie Nicht-Member-Nicht-Friend-Funktionen gegenüber Memberfunktionen. Scott Meyers betont hier die Verkapselung und das Ziel, das Expositionsrisiko zu reduzieren. Je mehr Details in einer Klasse verborgen sind, desto stabiler ist sie. Mit seiner Empfehlung für Nicht-Mitglieder-Funktionen plädiert er für Minimalismus und strikte Kapselung. 

Definieren Sie Nicht-Memberfunktionen, wenn Typkonvertierungen für alle Argumente erforderlich sind. 

Erwägen Sie die Implementierung eines nicht auslösenden Swap-Mechanismus. 

Entdecken Sie Boost. Über die C++-Standardbibliothek hinaus erkennt Scott Meyers die Relevanz von Bibliotheken von Drittanbietern, insbesondere von Boost. Diese Erwähnung eines breiteren Entwicklungsökosystems spiegelt seinen pragmatischen Ansatz wider und ermutigt Entwickler, bestehende Lösungen zu nutzen, anstatt alles von Grund auf neu zu erfinden. 

Scott Meyers‘ effektiver C++-Code-Check durch Understand

SciTools Understand bietet die Möglichkeit, Ihren Code anhand von veröffentlichten Codierungsstandards oder Ihren eigenen Standards zu analysieren.

Diese Checks können verwendet werden, um Benennungsrichtlinien, Metrikanforderungen, veröffentlichte Best Practices und andere Regeln oder Konventionen zu überprüfen, die für Ihr Team wichtig sind. 

Die folgenden Codierungsstandards werden in Understand untersucht: 

• Effektives C++ (3. Auflage) von Scott Meyers 

• AUTOSAR C++14 

• CERT C 

• CERT C++ 

• MISRA-C 2004 

• MISRA-C++ 2008 

• MISRA-C 2012/2023 

Eine vollständige, aktuelle Liste der Understand-Abdeckung für alle Codierungsstandards finden Sie hier 

Zusätzlich zur Abdeckung vieler der „Effective C++“-Richtlinien von Scott Meyers können Entwickler Understand in Verbindung mit Prinzipien aus Meyers‘ Büchern verwenden, um die Codequalität, die Wartbarkeit und das Verständnis der Entwickler zu verbessern! 

SciTools Understand bietet:

1. Komplexitätsmetriken:

Eines der Prinzipien von „Effektives C++“ besteht darin, einfachen und unkomplizierten C++-Code zu schreiben.

Understand bietet verschiedene Metriken (z. B. zyklomatische Komplexität, Codezeilen usw.), welche Entwicklern helfen können, komplexe oder dichte Teile der Codebasis zu identifizieren.

Durch die Überprüfung und mögliche Umgestaltung dieser Abschnitte können Entwickler Code erstellen, der den Richtlinien von Meyers entspricht. 

2. Abhängigkeitsanalyse:

Meyers betont, wie wichtig es ist, Abhängigkeiten zu verstehen und Objektbeziehungen effektiv zu verwalten.

Mit SciTools Understand können Entwickler Abhängigkeitsdiagramme visualisieren, die eine enge Kopplung zwischen Komponenten hervorheben.

Diese Visualisierung kann die Refactoring-Bemühungen leiten, um eine sauberere und modularere Architektur zu erreichen. 

3. Code-Dokumentation:

Eine angemessene Dokumentation stellt sicher, dass der Code lesbar und wartbar ist.

Understand bietet Tools zum Überprüfen und Generieren von Dokumentation, mit denen Teams sicherstellen können, dass die Codebasis in Übereinstimmung mit Meyers‘ Richtlinien für Codeklarheit gut dokumentiert ist.

Die Konvergenz von SciTools Understand und Scott Meyers‘ Effective C++

Die Prinzipien von Scott Meyers in „Effective C++“ bieten eine solide Grundlage für eine qualitativ hochwertige C++-Programmierung.

Seine Empfehlungen und Richtlinien zielen darauf ab, Code zu schreiben, der sowohl effizient als auch wartbar ist.

In diesem Zusammenhang ist SciTools Understand ein Tool, das perfekt zu den Ansätzen von Scott Meyers passt.

Es bietet eine umfassende Code-Analyse und erleichtert Entwicklern die Implementierung der Best Practices von Meyers in ihren Projekten. 

Durch die Verwendung von SciTools Understand können Entwickler sicherstellen, dass sie nicht nur Scott Meyers‘ Rat befolgen, sondern auch Code erstellen, der optimiert, verständlich und frei von versteckten Fehlern ist.

Das Tool ermöglicht es, die Komplexität des Codes zu visualisieren, Abhängigkeiten zu verstehen und potenzielle Problembereiche zu identifizieren, die möglicherweise übersehen wurden. 

Für Programmierer, die sich ständig weiterentwickeln und danach streben, ihr Handwerk zu perfektionieren, bietet SciTools Understand einen unschätzbaren Mehrwert.

Es verbessert nicht nur das Verständnis des eigenen Codes, sondern stellt auch sicher, dass die von ihnen erstellten Anwendungen den höchsten Industriestandards entsprechen.

Probieren Sie SciTools Understand aus und integrieren Sie es in Ihren Entwicklungsprozess! 

Textquellen

https://en.wikipedia.org/wiki/Scott_Meyers

https://github.com/gavincyi/Effective-Modern-Cpp

https://github.com/SahibYar/Effective-Cpp-Summary#effective-c-by-scott-meyers-short-summary – und alle anderen verwandten Seiten dieses Blogs 

https://support.scitools.com/support/solutions/articles/70000583268-what-standards-does-codecheck-test

https://support.scitools.com/support/solutions/articles/70000583282-codecheck-overview