715 Stimmen

Wann sollte ich noexcept wirklich verwenden?

En noexcept Schlüsselwort kann auf viele Funktionssignaturen angewendet werden, aber ich bin mir nicht sicher, wann ich es in der Praxis verwenden sollte. Nach dem, was ich bisher gelesen habe, ist die in letzter Minute hinzugefügte noexcept scheint sich mit einigen wichtigen Problemen zu befassen, die entstehen, wenn Move-Konstruktoren werfen. Ich bin jedoch immer noch nicht in der Lage, zufriedenstellende Antworten auf einige praktische Fragen zu geben, die mich dazu veranlassten, mehr über folgende Themen zu lesen noexcept an erster Stelle.

  1. Es gibt viele Beispiele für Funktionen, von denen ich weiß, dass sie niemals ausgelöst werden, bei denen der Compiler dies aber nicht selbst bestimmen kann. Sollte ich anhängen noexcept zur Funktionsdeklaration in alle diese Fälle?

    Ich muss darüber nachdenken, ob ich die folgenden Zeilen anhängen muss oder nicht noexcept nach jede Funktionsdeklaration würde die Produktivität der Programmierer erheblich verringern (und wäre, offen gesagt, ein Ärgernis). In welchen Situationen sollte ich vorsichtiger sein mit der Verwendung von noexcept und in welchen Situationen kann ich mit dem impliziten noexcept(false) ?

  2. Wann kann ich realistischerweise eine Leistungsverbesserung erwarten, nachdem ich noexcept ? Nennen Sie insbesondere ein Beispiel für Code, für den ein C++-Compiler besseren Maschinencode erzeugen kann, nachdem er Folgendes hinzugefügt hat noexcept .

    Mir persönlich sind folgende Punkte wichtig noexcept wegen der größeren Freiheit, die der Compiler hat, um bestimmte Arten von Optimierungen sicher anzuwenden. Nutzen moderne Compiler die Vorteile von noexcept auf diese Weise? Wenn nicht, kann ich erwarten, dass einige von ihnen dies in naher Zukunft tun werden?

246voto

Pubby Punkte 50647

Ich denke, es ist noch zu früh, um eine Antwort auf die Frage nach den besten Praktiken zu geben, da noch nicht genug Zeit war, um sie in der Praxis anzuwenden. Hätte man die Frage nach Wurfspezifizierern gestellt, gleich nachdem sie auf den Markt kamen, wären die Antworten ganz anders ausgefallen als jetzt.

Ich muss darüber nachdenken, ob ich die folgenden Zeilen anhängen muss oder nicht noexcept nach jeder Funktionsdeklaration zu verwenden, würde die Produktivität der Programmierer erheblich verringern (und wäre, offen gesagt, eine Qual).

Nun, dann verwenden Sie es, wenn es offensichtlich ist, dass die Funktion niemals ausgelöst wird.

Wann kann ich realistischerweise eine Leistungsverbesserung erwarten, nachdem ich noexcept ? [...] Mir persönlich ist es wichtig noexcept wegen der größeren Freiheit, die der Compiler hat, um bestimmte Arten von Optimierungen sicher anzuwenden.

Es scheint, dass die größten Optimierungsgewinne durch Benutzeroptimierungen und nicht durch Compileroptimierungen erzielt werden, da die Möglichkeit besteht, die noexcept und die Überlastung des Systems. Die meisten Compiler folgen einer Methode zur Behandlung von Ausnahmen, die nicht bestraft werden, wenn man sie nicht auslöst. Daher bezweifle ich, dass sich auf der Ebene des Maschinencodes Ihres Codes viel (oder gar nichts) ändern würde, obwohl sich die Binärgröße durch das Entfernen des Behandlungscodes vielleicht verringern würde.

使用方法 noexcept in den großen Vier (Konstruktoren, Zuweisung, nicht Destruktoren, da diese bereits noexcept ) werden wahrscheinlich die besten Verbesserungen bewirken, da noexcept Prüfungen sind im Vorlagencode "üblich", z. B. in std Container. Zum Beispiel, std::vector wird die Bewegung Ihrer Klasse nur verwenden, wenn sie markiert ist noexcept (oder der Compiler kann es anders ableiten).

177voto

Matthieu M. Punkte 266317

Wie ich in diesen Tagen immer wieder betone: Semantik zuerst .

Hinzufügen von noexcept , noexcept(true) y noexcept(false) geht es in erster Linie um Semantik. Sie bedingt nur nebenbei eine Reihe von möglichen Optimierungen.

Für einen Programmierer, der Code liest, ist das Vorhandensein von noexcept ähnelt dem von const : Es hilft mir, besser zu verstehen, was passieren kann oder nicht. Es lohnt sich also, sich Gedanken darüber zu machen, ob man weiß, ob die Funktion einen Fehler macht oder nicht. Zur Erinnerung: Jede Art von dynamischer Speicherzuweisung kann einen Fehler verursachen.


Okay, nun zu den möglichen Optimierungen.

Die offensichtlichsten Optimierungen werden in den Bibliotheken vorgenommen. C++11 bietet eine Reihe von Merkmalen, die es ermöglichen zu erkennen, ob eine Funktion noexcept oder nicht, und die Standardbibliothek-Implementierung selbst wird diese Eigenschaften verwenden, um die noexcept Operationen auf den benutzerdefinierten Objekten, die sie manipulieren, wenn möglich. Wie zum Beispiel Semantik verschieben .

Der Compiler kann nur ein wenig Fett (vielleicht) von den Daten für die Behandlung von Ausnahmen abschneiden, weil er hat die Tatsache zu berücksichtigen, dass Sie möglicherweise gelogen haben. Wenn eine Funktion mit noexcept wirft, dann std::terminate genannt wird.

Diese Semantik wurde aus zwei Gründen gewählt:

  • sofort profitieren von noexcept auch wenn die Abhängigkeiten es noch nicht verwenden (Abwärtskompatibilität)
  • die die Angabe von noexcept beim Aufruf von Funktionen, die theoretisch einen Fehler auslösen können, dies aber für die angegebenen Argumente nicht erwartet wird

111voto

Terry Mahaffey Punkte 11439

Dies macht tatsächlich einen (potenziell) großen Unterschied für den Optimierer im Compiler aus. Compiler haben diese Funktion schon seit Jahren über die leere throw()-Anweisung nach einer Funktionsdefinition sowie über proprietäre Erweiterungen. Ich kann Ihnen versichern, dass moderne Compiler dieses Wissen ausnutzen, um besseren Code zu erzeugen.

Fast jede Optimierung im Compiler verwendet einen so genannten "Flussgraphen" einer Funktion, um herauszufinden, was legal ist. Ein Flussdiagramm besteht aus so genannten "Blöcken" der Funktion (Codebereiche mit einem einzigen Eingang und einem einzigen Ausgang) und Kanten zwischen den Blöcken, die angeben, wohin der Fluss springen kann. Noexcept verändert den Flussgraphen.

Sie haben nach einem konkreten Beispiel gefragt. Betrachten Sie diesen Code:

void foo(int x) {
    try {
        bar();
        x = 5;
        // Other stuff which doesn't modify x, but might throw
    } catch(...) {
        // Don't modify x
    }

    baz(x); // Or other statement using x
}

Das Flussdiagramm für diese Funktion sieht anders aus, wenn bar wird bezeichnet als noexcept (es gibt keine Möglichkeit für die Ausführung, zwischen dem Ende von bar und die catch-Anweisung). Bei der Kennzeichnung als noexcept ist der Compiler sicher, dass der Wert von x während der baz-Funktion 5 ist - der x=5-Block "dominiert" den baz(x)-Block ohne den Rand von bar() an die catch-Anweisung.

Es kann dann etwas tun, das "Konstantenfortpflanzung" genannt wird, um effizienteren Code zu erzeugen. Wenn baz inlined ist, können die Anweisungen, die x verwenden, auch Konstanten enthalten, und was früher eine Laufzeitauswertung war, kann dann in eine Kompilierzeitauswertung umgewandelt werden, usw.

Wie auch immer, die kurze Antwort: noexcept lässt den Compiler einen engmaschigeren Flussgraphen generieren, und der Flussgraph wird verwendet, um über alle Arten von gängigen Compiler-Optimierungen nachzudenken. Für einen Compiler sind Benutzeranmerkungen dieser Art großartig. Der Compiler wird versuchen, diese Dinge herauszufinden, kann es aber in der Regel nicht (die fragliche Funktion könnte sich in einer anderen Objektdatei befinden, die für den Compiler nicht sichtbar ist, oder er verwendet vorübergehend eine Funktion, die nicht sichtbar ist), oder wenn er es doch tut, gibt es eine triviale Ausnahme, die ausgelöst werden könnte, von der Sie nicht einmal etwas wissen, so dass er sie nicht implizit als noexcept (die Zuweisung von Speicher könnte z.B. bad_alloc auslösen).

69voto

Andrzej Punkte 4643

noexcept kann die Leistung mancher Vorgänge drastisch verbessern. Dies geschieht nicht auf der Ebene der Erzeugung von Maschinencode durch den Compiler, sondern durch die Auswahl des effektivsten Algorithmus: Wie bereits erwähnt, erfolgt diese Auswahl mit der Funktion std::move_if_noexcept . Zum Beispiel ist das Wachstum der std::vector (z.B., wenn wir aufrufen reserve ) muss eine starke Garantie für die Ausnahmesicherheit bieten. Wenn sie weiß, dass T Der move-Konstruktor wirft nicht, er kann einfach jedes Element verschieben. Andernfalls muss er alle T s. Dies wurde ausführlich beschrieben in diese Stelle .

49voto

Nicol Bolas Punkte 409659

Wann kann ich realistischerweise mit einer Leistungsverbesserung rechnen, nachdem ich noexcept ? Nennen Sie insbesondere ein Beispiel für Code, für den ein C++-Compiler nach Hinzufügen von noexcept besseren Maschinencode erzeugen kann.

Ähm, niemals? Ist nie eine Zeit? Niemals.

noexcept ist für Compiler Leistungsoptimierungen in der gleichen Weise, wie const dient der Optimierung der Compilerleistung. Das heißt, fast nie.

noexcept wird in erster Linie verwendet, um "Ihnen" die Möglichkeit zu geben, zur Kompilierzeit zu erkennen, ob eine Funktion eine Ausnahme auslösen kann. Denken Sie daran: Die meisten Compiler geben keinen speziellen Code für Ausnahmen aus, es sei denn, es wird tatsächlich etwas geworfen. Also noexcept geht es nicht darum, dem Compiler Hinweise zu geben, wie eine Funktion zu optimieren ist, sondern vielmehr darum, die Sie Hinweise auf die Verwendung einer Funktion.

Schablonen wie move_if_noexcept erkennt, ob der move-Konstruktor mit noexcept und gibt eine const& anstelle einer && des Typs, wenn es nicht der Fall ist. Es ist eine Art zu sagen, dass man sich bewegen soll, wenn es sehr sicher ist, dies zu tun.

Im Allgemeinen sollten Sie noexcept wann Sie glauben, dass es tatsächlich sein wird nützlich zu tun. Einige Codes werden andere Wege gehen, wenn is_nothrow_constructible für diesen Typ gilt. Wenn Sie Code verwenden, der das tut, dann können Sie noexcept geeignete Konstrukteure.

Kurz gesagt: Verwenden Sie es für Move-Konstruktoren und ähnliche Konstrukte, aber glauben Sie nicht, dass Sie damit verrückt werden müssen.

CodeJaeger.com

CodeJaeger ist eine Gemeinschaft für Programmierer, die täglich Hilfe erhalten..
Wir haben viele Inhalte, und Sie können auch Ihre eigenen Fragen stellen oder die Fragen anderer Leute lösen.

Powered by:

X