C++17 im Detail:Sprachklärungen

C++17 im Detail:Sprachklärungen

Der zweite Teil meiner Serie über C++17 Details. Heute möchte ich mich auf Funktionen konzentrieren, die einige knifflige Teile der Sprache klären. Beispiel:Kopierausschluss und Ausdrucksauswertungsreihenfolge.

Einführung

Sie alle wissen das … C++ ist eine sehr komplexe Sprache, und einige (oder die meisten?:)) Teile sind ziemlich verwirrend. Einer der Gründe für die Unklarheit könnte eine freie Wahl für die Implementierung/den Compiler sein - zum Beispiel aggressivere Optimierungen zu ermöglichen oder rückwärts (oder C-)kompatibel zu sein. Manchmal ist es einfach ein Mangel an Zeit/Mühe/Zusammenarbeit. C++17 überprüft einige der beliebtesten „Löcher“ und behebt sie. Am Ende bekommen wir etwas klarer, wie die Dinge funktionieren könnten.

Heute möchte ich erwähnen über:

  • Bewertungsauftrag
  • Kopieren Sie Elision (optionale Optimierung, die in allen populären Compilern implementiert zu sein scheint)
  • Ausnahmen
  • Speicherzuweisungen für (über)ausgerichtete Daten

Die Serie

Dieser Beitrag ist der zweite in der Reihe über C++17-Funktionsdetails.

Der Plan für die Serie

  1. Korrekturen und Einstellung
  2. Sprachklärung (heute )
  3. Vorlagen
  4. Attribute
  5. Vereinfachung
  6. Bibliothek ändert -Dateisystem
  7. Bibliotheksänderungen - ParallelAlgorithms
  8. Bibliotheksänderungen -Utils
  9. Abschluss, Bonus – mit einem kostenlosen E-Book! :)

Nur zur Erinnerung:

Wenn Sie sich zunächst selbst mit dem Standard befassen möchten, können Sie den neuesten Entwurf hier lesen:

N4659, 2017-03-21, Arbeitsentwurf, Standard für die Programmiersprache C++

  • der Link erscheint auch auf isocpp.org.

Compilerunterstützung:C++-Compilerunterstützung

Außerdem habe ich eine Liste mit kurzen Beschreibungen aller Sprachfunktionen von C++17 vorbereitet:

Laden Sie eine kostenlose Kopie meines C++17 CheatSheets herunter!

Es ist eine einseitige Referenzkarte, PDF.

Es gibt auch einen Vortrag von Bryce Lelbach:C++Now 2017:C++17Features

Strengere Reihenfolge der Ausdrucksauswertung

Dieser ist schwierig, also korrigieren Sie mich bitte, wenn ich hier falsch liege, und lassen Sie mich wissen, ob Sie weitere Beispiele und bessere Erklärungen haben. Ich habe versucht, einige Details auf Slack/Twitter zu bestätigen, und hoffe, dass ich hier keinen Unsinn schreibe :)

Versuchen wir:

C++ gibt keine Auswertungsreihenfolge für Funktionsparameter an.Punkt.

Deshalb zum Beispiel make_unique ist nicht nur syntaktischer Zucker, sondern garantiert Gedächtnissicherheit:

Mit make_unique :

foo(make_unique<T>(), otherFunction());

Und zwar explizit mit new .

foo(unique_ptr<T>(new T), otherFunction());

Im obigen Code wissen wir, dass new T geschieht garantiert vorunique_ptr Aufbau, aber das ist alles. Beispiel:new T könnte zuerst passieren, dann otherFunction() , und dann unique_ptr Konstrukteur.
Wenn otherFunction throws, dann new T erzeugt ein Leck (da der eindeutige Zeiger noch nicht erstellt wurde). Wenn Sie make_unique verwenden , dann ist ein Durchsickern nicht möglich, selbst wenn die Reihenfolge der Ausführung zufällig ist. Weitere solcher Probleme in GotW #56:Exception-Safe FunctionCalls

Beim akzeptierten Vorschlag sollte die Bewertungsreihenfolge ‚praktisch‘ sein.

Beispiele:

  • in f(a, b, c) - Die Reihenfolge der Auswertung von a, b, c ist noch nicht spezifiziert, aber jeder Parameter wird vollständig ausgewertet, bevor der nächste gestartet wird. Besonders wichtig für komplexe Ausdrücke.
    • wenn ich richtig liege, behebt das ein Problem mit make_unique vsunique_ptr<T>(new T()) . Als Funktionsargument muss es vor anderen Argumenten vollständig ausgewertet werden.
  • Die Verkettung von Funktionen funktioniert bereits von links nach rechts, aber die Reihenfolge der Auswertung innerer Ausdrücke kann unterschiedlich sein. siehe hier:c++11 – Hat dieser Code aus „The C++ Programming Language“ 4thedition Abschnitt 36.3.6 ein wohldefiniertes Verhalten? - StackOverflow. Um richtig zu sein:„Die Ausdrücke sind in Bezug aufeinander unbestimmt sequenziert“, siehe Sequence Point Mehrdeutigkeit, undefiniertes Verhalten?.
  • Mit C++17 funktioniert die Verkettung von Funktionen jetzt wie erwartet, wenn sie solche inneren Ausdrücke enthalten, d. h. sie werden von links nach rechts ausgewertet:a(expA).b(expB).c(expC) wird von links nach rechts ausgewertet und expA wird vor dem Aufruf von b…
  • ausgewertet
  • Bei der Verwendung von Operatorüberladungen wird die Auswertungsreihenfolge durch die Reihenfolge bestimmt, die dem entsprechenden eingebauten Operator zugeordnet ist:
    • also std::cout << a() << b() << c() wird als a, b, c gewertet.

Und aus der Zeitung:

Und der wichtigste Teil der Spezifikation ist wahrscheinlich:

StackOverflow:Welche Garantien für die Bewertungsreihenfolge wurden eingeführt? von C++17?

Weitere Details in:P0145R3 und P0400R0. In Visual Studio 2017, GCC 7.0, Clang 4.0 noch nicht unterstützt

Garantiertes Entfernen von Kopien

Derzeit erlaubt der Standard das Eliding in folgenden Fällen:

  • wenn ein temporäres Objekt verwendet wird, um ein anderes Objekt zu initialisieren (einschließlich des von einer Funktion zurückgegebenen Objekts oder des durch einen Throw-Ausdruck erstellten Ausnahmeobjekts)
  • wenn eine Variable, die den Geltungsbereich zu verlassen droht, zurückgegeben oder ausgelöst wird
  • wenn eine Ausnahme vom Wert abgefangen wird

Aber es ist Sache des Compilers/der Implementierung, sich zu eliminieren oder nicht. In der Praxis sind alle Definitionen der Konstrukteure erforderlich. Manchmal kann es vorkommen, dass Elision nur in Release-Builds (optimiert) passiert, während Debug-Builds (ohne jegliche Optimierung) nichts auslassen.

Mit C++17 erhalten wir klare Regeln, wann Elision auftritt, und daher können Konstruktoren vollständig weggelassen werden.

Warum könnte es nützlich sein?

  • Zurückgeben von Objekten, die nicht verschiebbar/kopierbar sind - weil wir jetzt Kopier-/Verschiebekonstruktoren überspringen könnten. Nützlich in Fabriken.
  • Verbessern Sie die Portabilität des Codes, unterstützen Sie das Muster „Rückgabe nach Wert“, anstatt „Ausgabeparameter“ zu verwenden.

Beispiel:

// based on P0135R0
struct NonMoveable 
{
  NonMoveable(int);
  // no copy or move constructor:
  NonMoveable(const NonMoveable&) = delete;
  NonMoveable(NonMoveable&&) = delete;

  std::array<int, 1024> arr;
};

NonMoveable make() 
{
  return NonMoveable(42);
}

// construct the object:
auto largeNonMovableObj = make();

Der obige Code würde nicht unter C++14 kompiliert werden, da ihm Copy- und Move-Konstruktoren fehlen. Aber mit C++17 werden die Konstruktoren nicht benötigt - weil das Objekt largeNonMovableObj wird an Ort und Stelle gebaut.

Das Definieren von Regeln für das Entfernen von Kopien ist nicht einfach, aber die Autoren des Vorschlags schlugen neue, vereinfachte Typen von Wertkategorien vor:

  • glvalue - „A glvalue ist ein Ausdruck, dessen Auswertung den Ort eines Objekts, Bitfelds oder einer Funktion berechnet. ‘
  • prvalue - Ein prvalue ist ein Ausdruck, dessen Auswertung ein Objekt, ein Bitfeld oder einen Operanden eines Operators initialisiert, wie durch den Kontext angegeben, in dem er erscheint

Kurz:prvalues Initialisierung durchführen, glvalues Produktionsstandorte.

Leider erhalten wir in C++17 Copy Elision nur für temporäre Objekte, nicht für Named RVO (es deckt also nur den ersten Punkt ab, nicht für Named Return Value Optimization). Vielleicht folgt C++20 und fügt hier weitere Regeln hinzu?

Weitere Details:P0135R0, MSVC 2017:noch nicht . GCC:7.0, Clang:4.0.

Ausnahmespezifikationen Teil des Typsystems

Früher gehörten Ausnahmespezifikationen für eine Funktion nicht zum Typ der Funktion, aber jetzt werden sie Teil davon sein.

Wir erhalten einen Fehler in dem Fall:

void (*p)();
void (**pp)() noexcept = &p; // error: cannot convert to
                         // pointer to noexcept function

struct S { typedef void (*p)(); operator p(); };
void (*q)() noexcept = S(); // error: cannot convert to 
                            // pointer to noexcept

Einer der Gründe für das Hinzufügen der Funktion ist die Möglichkeit, eine bessere Optimierung zu ermöglichen. Das kann passieren, wenn Sie eine Garantie haben, dass eine Funktion zum Beispiel noexcept ist .

Auch in C++17 wird die Exception-Spezifikation aufgeräumt:RemovingDeprecated Exception Specifications fromC++17

  • es handelt sich um sogenannte „dynamische Ausnahmespezifikationen“. Effektiv können Sie nur noexcept verwenden Bezeichner für die Deklaration, ob eine Funktion etwas auslösen kann oder nicht.

Weitere Details:P0012R1, MSVC 2017:noch nicht , GCC 7.0, Clang 4.0.

Dynamische Speicherzuordnung für over-aligned-Daten

Wenn Sie SIMD durchführen oder wenn Sie andere Anforderungen an das Speicherlayout haben, müssen Sie möglicherweise Objekte speziell ausrichten. Beispielsweise benötigen Sie in SSE ein 16-Byte-Alignment (für AVX 256 benötigen Sie ein 32-Byte-Alignment). Sie würden also einen Vektor4 wie folgt definieren:

class alignas(16) vec4 
{
    float x, y, z, w;
};
auto pVectors = new vec4[1000];

Hinweis:ausrichten Bezeichner ist ab C++11 verfügbar.

In C++11/14 haben Sie keine Garantie, wie der Speicher ausgerichtet wird. Manchmal müssen Sie einige spezielle Routinen wie _aligned_malloc verwenden /_aligned_free um sicherzustellen, dass die Ausrichtung erhalten bleibt. Das ist nicht schön, da es nicht mit intelligenten C++-Zeigern funktioniert und auch Speicherzuweisungen/Löschungen im Code sichtbar macht (wir sollten aufhören, raw newand delete zu verwenden, gemäß den Kernrichtlinien).

C++17 behebt diese Lücke, indem es zusätzliche Speicherzuweisungsfunktionen einführt, die den Parameter align verwenden:

void* operator new(size_t, align_val_t);
void* operator new[](size_t, align_val_t);
void operator delete(void*, align_val_t);
void operator delete[](void*, align_val_t);
void operator delete(void*, size_t, align_val_t);
void operator delete[](void*, size_t, align_val_t);

Jetzt können Sie diesen vec4 zuweisen Array als:

auto pVectors = new vec4[1000];

Keine Codeänderungen, aber es wird auf magische Weise aufgerufen:

operator new[](sizeof(vec4), align_val_t(alignof(vec4)))

Mit anderen Worten, new erkennt jetzt die Ausrichtung des Objekts.

Weitere Details in P0035R4. MSVC 2017:noch nicht , GCC:7.0, Clang:4.0.

Zusammenfassung

Heute haben wir uns auf vier Bereiche konzentriert, in denen die C++-Spezifikation jetzt klarer ist. Wir haben jetzt Möglichkeiten anzunehmen, dass Copy Ellison passieren wird, einige Operationen sind jetzt gut definiert, Operator new kennt nun die Ausrichtung eines Typs und auch Ausnahmen sind Teil der Funktionsdeklaration.

Was sind Ihre Tipps zur sprachlichen Klärung?

Welche anderen „Löcher“ müssen gefüllt werden?

Beim nächsten Mal werden wir uns mit Änderungen für Vorlagen und generische Programmierung befassen. Bleiben Sie dran!

Denken Sie noch einmal daran, sich meine C++17 Language RefCard zu schnappen .