Wann sollte ich noexcept wirklich verwenden?

507

Das noexceptSchlüsselwort kann angemessen auf viele Funktionssignaturen angewendet werden, aber ich bin mir nicht sicher, wann ich es in der Praxis verwenden soll. Basierend auf dem, was ich bisher gelesen habe, noexceptscheint die Hinzufügung von Last-Minute einige wichtige Probleme zu lösen, die beim Werfen von Bewegungskonstruktoren auftreten. Ich kann jedoch immer noch keine zufriedenstellenden Antworten auf einige praktische Fragen geben, über die ich überhaupt mehr gelesen habe noexcept.

  1. Es gibt viele Beispiele für Funktionen, von denen ich weiß, dass sie niemals ausgelöst werden, für die der Compiler dies jedoch nicht selbst bestimmen kann. Sollte ich noexceptin all diesen Fällen an die Funktionsdeklaration anhängen ?

    Überlegen zu müssen, ob ich noexceptnach jeder Funktionsdeklaration anhängen muss oder nicht, würde die Produktivität des Programmierers erheblich verringern (und wäre ehrlich gesagt ein Ärgernis). In welchen Situationen sollte ich bei der Verwendung vorsichtiger sein noexceptund in welchen Situationen kann ich mit dem Implizierten davonkommen noexcept(false)?

  2. Wann kann ich realistisch erwarten, nach der Verwendung eine Leistungsverbesserung zu beobachten noexcept? Geben Sie insbesondere ein Beispiel für Code an, für den ein C ++ - Compiler nach dem Hinzufügen von besseren Maschinencode generieren kann noexcept.

    Persönlich ist mir wichtig noexcept, dass der Compiler mehr Freiheit hat, bestimmte Arten von Optimierungen sicher anzuwenden. Nutzen moderne Compiler diese Vorteile noexcept? Wenn nicht, kann ich erwarten, dass einige von ihnen dies in naher Zukunft tun?

Leerzeiger
quelle
40
Code, der verwendet move_if_nothrow(oder whatchamacallit), wird eine Leistungsverbesserung feststellen, wenn es keine Ausnahmeverschiebung gibt.
R. Martinho Fernandes
Siehe auch
5
Es ist move_if_noexcept.
Nikos

Antworten:

180

Ich denke, es ist zu früh, um eine Antwort auf "Best Practices" zu geben, da nicht genügend Zeit vorhanden war, um sie in der Praxis anzuwenden. Wenn dies direkt nach dem Erscheinen nach Wurfspezifizierern gefragt würde, wären die Antworten ganz anders als jetzt.

Überlegen zu müssen, ob ich noexceptnach jeder Funktionsdeklaration anhängen muss oder nicht, würde die Produktivität des Programmierers erheblich verringern (und wäre ehrlich gesagt ein Schmerz).

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

Wann kann ich realistisch erwarten, nach der Verwendung eine Leistungsverbesserung zu beobachten noexcept? [...] Persönlich ist mir wichtig noexcept, dass der Compiler mehr Freiheit hat, bestimmte Arten von Optimierungen sicher anzuwenden.

Es scheint, dass die größten Optimierungsgewinne aus Benutzeroptimierungen resultieren, nicht aus Compileroptimierungen, da die Möglichkeit besteht, diese zu überprüfen noexceptund zu überladen. Die meisten Compiler folgen einer Ausnahmebehandlungsmethode ohne Strafe, wenn Sie keine Ausnahme auslösen. Daher bezweifle ich, dass sich auf der Ebene des Maschinencodes Ihres Codes viel (oder nichts) ändern würde, obwohl Sie möglicherweise die Binärgröße durch Entfernen der verringern Umgang mit Code.

Die Verwendung noexceptin den großen vier (Konstruktoren, Zuweisungen, nicht Destruktoren, wie sie bereits sind noexcept) führt wahrscheinlich zu den besten Verbesserungen, da noexceptÜberprüfungen im Vorlagencode, z. B. in stdContainern, häufig vorkommen. Zum Beispiel std::vectorwird Ihre Klassen bewegen verwenden , wenn es markiert ist noexcept(oder der Compiler kann es anders ableiten).

Pubby
quelle
6
Ich denke, der std::terminateTrick gehorcht immer noch dem Zero-Cost-Modell. Das heißt, es kommt einfach so vor, dass der Befehlsbereich innerhalb der noexceptFunktionen dem Aufruf zugeordnet wird, std::terminatewenn thrower anstelle des Stapelabwicklers verwendet wird. Ich bezweifle daher, dass es mehr Overhead hat als die reguläre Ausnahmeverfolgung.
Matthieu M.
4
@Klaim Siehe dies: stackoverflow.com/a/10128180/964135 Eigentlich muss es nur nicht werfen, aber das noexceptgarantiert dies.
Pubby
3
"Wenn eine noexceptFunktion ausgelöst wird, std::terminatewird aufgerufen, was anscheinend einen geringen Overhead mit sich bringt." ... Nein, dies sollte implementiert werden, indem für eine solche Funktion keine Ausnahmetabellen generiert werden, die der Ausnahmeverteiler abfangen und dann retten sollte.
Potatoswatter
8
Die Behandlung von @Pubby C ++ - Ausnahmen erfolgt normalerweise ohne Overhead, mit Ausnahme von Sprungtabellen, die potenziell auslösende Call-Site-Adressen Handler-Einstiegspunkten zuordnen. Das Entfernen dieser Tabellen ist so nah wie möglich am vollständigen Entfernen der Ausnahmebehandlung. Der einzige Unterschied ist die Größe der ausführbaren Datei. Wahrscheinlich nichts Erwähnenswertes.
Potatoswatter
26
"Na dann benutze es, wenn es offensichtlich ist, dass die Funktion niemals werfen wird." Ich stimme dir nicht zu. noexceptist Teil der Funktion der Schnittstelle ; Sie sollten es nicht hinzufügen, nur weil Ihre aktuelle Implementierung nicht wirft. Ich bin mir nicht sicher über die richtige Antwort auf diese Frage, aber ich bin ziemlich zuversichtlich, dass das Verhalten Ihrer Funktion heute nichts damit zu tun hat ...
Nemo
133

Wie ich heutzutage immer wieder wiederhole: Semantik zuerst .

Hinzufügen noexcept, noexcept(true)und noexcept(false)geht es in erster Linie um Semantik. Es bedingt nur nebenbei eine Reihe möglicher Optimierungen.

Als Programmierer, der Code liest, noexceptähnelt das Vorhandensein von const: Es hilft mir, besser zu verstehen, was passieren kann oder nicht. Daher lohnt es sich, einige Zeit darüber nachzudenken, ob Sie wissen, ob die Funktion ausgelöst wird oder nicht. Zur Erinnerung kann jede Art von dynamischer Speicherzuweisung ausgelöst werden.


Okay, nun zu den möglichen Optimierungen.

Die offensichtlichsten Optimierungen werden tatsächlich in den Bibliotheken durchgeführt. C ++ 11 bietet eine Reihe von Merkmalen, mit denen Sie feststellen können, ob eine Funktion vorhanden ist noexceptoder nicht, und die Standardbibliotheksimplementierung selbst verwendet diese Merkmale, um noexceptOperationen an den von ihnen manipulierten benutzerdefinierten Objekten nach Möglichkeit zu bevorzugen . Wie Bewegungssemantik .

Der Compiler kann nur ein wenig Fett (vielleicht) von der Ausnahme rasiert Datenhandling, weil es hat die Tatsache zu berücksichtigen , dass Sie gelogen haben. Wenn eine markierte Funktion noexceptwirft, std::terminatewird aufgerufen.

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

  • sofort davon profitieren, noexceptauch wenn Abhängigkeiten es nicht bereits nutzen (Abwärtskompatibilität)
  • Ermöglichen der Angabe noexceptbeim Aufrufen von Funktionen, die theoretisch auslösen können, aber für die angegebenen Argumente nicht erwartet werden
Matthieu M.
quelle
2
Vielleicht bin ich naiv, aber ich würde mir vorstellen, dass eine Funktion, die nur noexceptFunktionen aufruft , nichts Besonderes tun muss, da eventuell auftretende Ausnahmen ausgelöst werden, terminatebevor sie diese Ebene erreichen. Dies unterscheidet sich stark von der Notwendigkeit, eine bad_allocAusnahme zu behandeln und zu verbreiten .
8
Ja, es ist möglich, noexcept so zu definieren, wie Sie es vorschlagen, aber das wäre eine wirklich unbrauchbare Funktion. Viele Funktionen können ausgelöst werden, wenn bestimmte Bedingungen nicht erfüllt sind, und Sie können sie nicht aufrufen, selbst wenn Sie wissen, dass die Bedingungen erfüllt sind. Zum Beispiel jede Funktion, die std :: invalid_argument auslösen kann.
tr3w
3
@MatthieuM. Ein bisschen spät für eine Antwort, aber trotzdem. Mit noexcept gekennzeichnete Funktionen können andere Funktionen aufrufen, die auslösen können. Das Versprechen ist, dass diese Funktion keine Ausnahme ausgibt, dh sie müssen die Ausnahme nur selbst behandeln!
Honf
4
Ich habe diese Antwort vor langer Zeit positiv bewertet, aber nachdem ich sie noch einmal gelesen und darüber nachgedacht habe, habe ich einen Kommentar / eine Frage. "Bewegungssemantik" ist das einzige Beispiel, das ich jemals gesehen habe, wo jemand noexcepteindeutig hilfreich / eine gute Idee ist. Ich fange an zu denken, dass Umzugskonstruktion, Umzugszuweisung und Tausch die einzigen Fälle sind, die es gibt ... Kennen Sie andere?
Nemo
5
@Nemo: In der Standardbibliothek ist es möglicherweise die einzige, weist jedoch ein Prinzip auf, das an anderer Stelle wiederverwendet werden kann. Eine Verschiebungsoperation ist eine Operation, die vorübergehend einen Zustand in die "Schwebe" versetzt, und nur wenn dies noexceptder Fall ist, kann jemand sie sicher für ein Datenelement verwenden, auf das später zugegriffen werden kann. Ich konnte sehen, dass diese Idee an anderer Stelle verwendet wird, aber die Standardbibliothek ist in C ++ ziemlich dünn und wird nur verwendet, um Kopien von Elementen zu optimieren, die ich denke.
Matthieu M.
77

Dies macht tatsächlich einen (möglicherweise) großen Unterschied zum Optimierer im Compiler. Compiler haben diese Funktion tatsächlich seit Jahren über die leere Anweisung throw () nach einer Funktionsdefinition sowie über Propriety-Erweiterungen. Ich kann Ihnen versichern, dass moderne Compiler dieses Wissen nutzen, um besseren Code zu generieren.

Fast jede Optimierung im Compiler verwendet ein sogenanntes "Flussdiagramm" einer Funktion, um zu überlegen, was legal ist. Ein Flussdiagramm besteht aus sogenannten "Blöcken" der Funktion (Codebereiche mit einem einzigen Eingang und einem einzigen Ausgang) und Kanten zwischen den Blöcken, um anzugeben, wohin der Fluss springen kann. Noexcept ändert das Flussdiagramm.

Sie haben nach einem bestimmten 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 unterscheidet sich, wenn bares beschriftet ist noexcept(es gibt keine Möglichkeit für die Ausführung, zwischen dem Ende barund der catch-Anweisung zu springen ). Bei der Bezeichnung als noexceptist der Compiler sicher, dass der Wert von x während der baz-Funktion 5 ist - der Block x = 5 soll den baz (x) -Block ohne die Kante von bar()bis zur catch-Anweisung "dominieren" .

Es kann dann etwas tun, das als "konstante Ausbreitung" bezeichnet wird, um effizienteren Code zu erzeugen. Wenn baz inline ist, können die Anweisungen, die x verwenden, auch Konstanten enthalten, und was früher eine Laufzeitauswertung war, kann in eine Auswertung zur Kompilierungszeit usw. umgewandelt werden.

Wie auch immer, die kurze Antwort: noexceptDer Compiler kann ein engeres Flussdiagramm erstellen, und das Flussdiagramm wird verwendet, um über alle möglichen gängigen Compileroptimierungen nachzudenken. Für einen Compiler sind Benutzeranmerkungen dieser Art fantastisch. Der Compiler wird versuchen, dieses Problem zu lösen, kann dies jedoch normalerweise nicht (die betreffende Funktion befindet sich möglicherweise in einer anderen Objektdatei, die für den Compiler nicht sichtbar ist, oder verwendet transitiv eine Funktion, die nicht sichtbar ist), oder wenn dies der Fall ist, ist dies der Fall Eine triviale Ausnahme, die möglicherweise ausgelöst wird und die Sie nicht einmal kennen, sodass sie nicht implizit als gekennzeichnet werden kann noexcept(das Zuweisen von Speicher kann beispielsweise bad_alloc auslösen).

Terry Mahaffey
quelle
3
Macht dies in der Praxis tatsächlich einen Unterschied? Das Beispiel ist erfunden, weil nichts vorher x = 5werfen kann. Wenn dieser Teil des tryBlocks irgendeinen Zweck erfüllen würde, würde die Argumentation nicht gelten.
Potatoswatter
7
Ich würde sagen, es macht einen echten Unterschied bei der Optimierung von Funktionen, die Try / Catch-Blöcke enthalten. Das Beispiel, das ich gegeben habe, ist zwar erfunden, aber nicht erschöpfend. Der größere Punkt ist, dass noexcept (wie die Anweisung throw () davor) der Kompilierung hilft, ein kleineres Flussdiagramm (weniger Kanten, weniger Blöcke) zu generieren, was ein wesentlicher Bestandteil vieler Optimierungen ist, die es dann durchführt.
Terry Mahaffey
Wie kann der Compiler erkennen, dass der Code eine Ausnahme auslösen kann? Wird der Zugriff auf ein Array als mögliche Ausnahme angesehen?
Tomas Kubes
3
@ qub1n Wenn der Compiler den Hauptteil der Funktion sehen kann, kann er nach expliziten throwAnweisungen suchen oder andere Dinge wie newdiese auslösen. Wenn der Compiler den Body nicht sehen kann, muss er sich auf das Vorhandensein oder Fehlen von verlassen noexcept. Der einfache Array-Zugriff generiert normalerweise keine Ausnahmen (C ++ hat keine Überprüfung der Grenzen). Nein, der Array-Zugriff würde den Compiler nicht allein dazu veranlassen, zu glauben, dass eine Funktion Ausnahmen auslöst. (Out-of-Bound-Zugriff ist UB, keine garantierte Ausnahme.)
cdhowie
@cdhowie " es muss auf das Vorhandensein oder Fehlen von noexcept angewiesen sein " oder das Vorhandensein von throw()in Pre-noexcept C ++
neugieriger Kerl
57

noexceptkann die Leistung einiger Operationen dramatisch verbessern. Dies geschieht nicht auf der Ebene der Generierung von Maschinencode durch den Compiler, sondern durch Auswahl des effektivsten Algorithmus: Wie bereits erwähnt, führen Sie diese Auswahl mithilfe der Funktion durch std::move_if_noexcept. Zum Beispiel muss das Wachstum von std::vector(z. B. wenn wir anrufen reserve) eine starke Ausnahmesicherheitsgarantie bieten. Wenn es weiß, dass Tder Verschiebungskonstruktor nicht wirft, kann er einfach jedes Element verschieben. Andernfalls müssen alle Ts kopiert werden . Dies wurde in diesem Beitrag ausführlich beschrieben .

Andrzej
quelle
4
Nachtrag: Das heißt, wenn Sie Verschiebungskonstruktoren definieren oder Verschiebungszuweisungsoperatoren hinzufügen noexcept(falls zutreffend)! Implizit definierte Funktionen für Verschiebungselemente wurden noexceptautomatisch hinzugefügt (falls zutreffend).
Mucaho
33

Wann kann ich realistisch außer einer Leistungsverbesserung nach der Verwendung beobachten noexcept? Geben Sie insbesondere ein Beispiel für Code an, für den ein C ++ - Compiler nach dem Hinzufügen von noexcept besseren Maschinencode generieren kann.

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

noexceptist für Compiler- Leistungsoptimierungen genauso wie constfür Compiler-Leistungsoptimierungen. Das heißt, fast nie.

noexceptwird hauptsächlich verwendet, damit "Sie" zur Kompilierungszeit erkennen können, 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, er löst tatsächlich etwas aus. So noexceptist nicht eine Frage des Gebens der Compiler Hinweise darüber , wie eine Funktion so viel zu optimieren , geben Sie Hinweise darüber , wie eine Funktion zu verwenden.

Vorlagen wie move_if_noexcepterkennen, ob der Verschiebungskonstruktor mit definiert ist, noexceptund geben ein a const&anstelle eines &&Typs zurück, wenn dies nicht der Fall ist. Es ist eine Art zu sagen, sich zu bewegen, wenn es sehr sicher ist, dies zu tun.

Im Allgemeinen sollten Sie verwenden, noexceptwenn Sie der Meinung sind, dass dies tatsächlich nützlich ist . Einige Codes nehmen unterschiedliche Pfade, wenn is_nothrow_constructibledies für diesen Typ zutrifft. Wenn Sie Code verwenden, der dies ermöglicht, wenden Sie sich an die noexceptentsprechenden Konstruktoren.

Kurz gesagt: Verwenden Sie es für Verschiebungskonstruktoren und ähnliche Konstrukte, aber haben Sie nicht das Gefühl, dass Sie damit verrückt werden müssen.

Nicol Bolas
quelle
13
Streng genommen move_if_noexceptwird keine Kopie zurückgegeben, sondern eine const lvalue-Referenz anstelle einer rvalue-Referenz. Im Allgemeinen führt dies dazu, dass der Anrufer eine Kopie anstelle einer Verschiebung erstellt, move_if_noexceptdie Kopie jedoch nicht ausführt. Ansonsten tolle Erklärung.
Jonathan Wakely
12
+1 Jonathan. Wenn Sie beispielsweise die Größe eines Vektors ändern, werden die Objekte verschoben, anstatt sie zu kopieren, wenn der Verschiebungskonstruktor aktiviert ist noexcept. So dass "nie" nicht wahr ist.
Mfontanini
4
Ich meine, der Compiler wird in dieser Situation besseren Code generieren. OP fragt nach einem Beispiel, für das der Compiler eine optimierte Anwendung generieren kann. Dies scheint der Fall zu sein (obwohl es sich nicht um eine Compileroptimierung handelt ).
Mfontanini
7
@mfontanini: Der Compiler generiert nur besseren Code, da der Compiler gezwungen ist, einen anderen Codepfad zu kompilieren . Es funktioniert nur, weil std::vectores geschrieben wurde, um den Compiler zu zwingen, anderen Code zu kompilieren . Es geht nicht darum, dass der Compiler etwas erkennt. Es geht darum, dass Benutzercode etwas erkennt.
Nicol Bolas
3
Die Sache ist, ich kann "Compiler-Optimierung" im Zitat zu Beginn Ihrer Antwort nicht finden. Wie @ChristianRau sagte, generiert der Compiler einen effizienteren Code. Es spielt keine Rolle, woher diese Optimierung stammt. Schließlich ist der Compiler wird eine effizientere Code zu erzeugen, ist es nicht? PS: Ich habe nie gesagt, dass es eine Compiler-Optimierung ist, ich habe sogar gesagt, dass es keine Compiler-Optimierung ist.
Mfontanini
22

In Bjarnes Worten ( The C ++ Programming Language, 4. Ausgabe , Seite 366):

Wenn die Kündigung eine akzeptable Antwort ist, wird dies durch eine nicht erfasste Ausnahme erreicht, da sie zu einem Aufruf von terminate () wird (§13.5.2.5). Auch ein noexceptSpezifizierer (§13.5.1.1) kann diesen Wunsch explizit machen.

Erfolgreiche fehlertolerante Systeme sind mehrstufig. Jede Ebene bewältigt so viele Fehler wie möglich, ohne zu verzerrt zu werden, und überlässt den Rest höheren Ebenen. Ausnahmen unterstützen diese Ansicht. Darüber hinaus terminate()unterstützt diese Ansicht durch eine Flucht bereitstellt , wenn die Ausnahmebehandlung Mechanismus selbst ist verdorbenen oder wenn es nicht vollständig verwendet wurde, so dass Ausnahmen nicht abgefangene verlassen. Bietet in ähnlicher Weise noexcepteine einfache Flucht vor Fehlern, bei denen der Versuch einer Wiederherstellung nicht möglich erscheint.

double compute(double x) noexcept;     {
    string s = "Courtney and Anya";
    vector<double> tmp(10);
    // ...
}

Der Vektorkonstruktor kann möglicherweise keinen Speicher für seine zehn Doppelten erfassen und a werfen std::bad_alloc. In diesem Fall wird das Programm beendet. Es endet bedingungslos durch Aufrufen std::terminate()(§30.4.1.3). Destruktoren werden beim Aufrufen von Funktionen nicht aufgerufen. Es ist implementierungsdefiniert, ob Destruktoren aus Bereichen zwischen throwund noexcept(z. B. für s in compute ()) aufgerufen werden. Das Programm steht kurz vor dem Abschluss, daher sollten wir uns sowieso nicht auf ein Objekt verlassen. Durch Hinzufügen eines noexceptBezeichners weisen wir darauf hin, dass unser Code nicht für einen Wurf geschrieben wurde.

Saurav Sahu
quelle
2
Haben Sie eine Quelle für dieses Zitat?
Anton Golov
5
@AntonGolov "Die C ++ - Programmiersprache, 4. Ausgabe" pg. 366
Rusty Shackleford
Das klingt für mich so, als ob ich tatsächlich noexceptjedes Mal ein hinzufügen sollte , außer ich möchte mich ausdrücklich um Ausnahmen kümmern. Seien wir ehrlich, die meisten Ausnahmen sind so unwahrscheinlich und / oder so tödlich, dass eine Rettung kaum sinnvoll oder möglich ist. Wenn im angegebenen Beispiel beispielsweise die Zuordnung fehlschlägt, kann die Anwendung kaum ordnungsgemäß weiterarbeiten.
Neonit
21
  1. Es gibt viele Beispiele für Funktionen, von denen ich weiß, dass sie niemals ausgelöst werden, für die der Compiler dies jedoch nicht selbst bestimmen kann. Sollte ich in all diesen Fällen keine Ausnahme an die Funktionsdeklaration anhängen?

noexceptist schwierig, da es Teil der Funktionsschnittstelle ist. Insbesondere wenn Sie eine Bibliothek schreiben, kann Ihr Client-Code von der noexceptEigenschaft abhängen . Es kann schwierig sein, es später zu ändern, da Sie möglicherweise vorhandenen Code beschädigen. Dies ist möglicherweise weniger problematisch, wenn Sie Code implementieren, der nur von Ihrer Anwendung verwendet wird.

Wenn Sie eine Funktion haben, die nicht ausgelöst werden kann, fragen Sie sich, ob sie bleiben soll noexceptoder ob dies zukünftige Implementierungen einschränken würde. Beispielsweise möchten Sie möglicherweise eine Fehlerprüfung für unzulässige Argumente einleiten, indem Sie Ausnahmen auslösen (z. B. für Komponententests), oder Sie sind auf anderen Bibliothekscode angewiesen, der die Ausnahmespezifikation ändern kann. In diesem Fall ist es sicherer, konservativ zu sein und wegzulassen noexcept.

Wenn Sie jedoch sicher sind, dass die Funktion niemals ausgelöst werden sollte und es richtig ist, dass sie Teil der Spezifikation ist, sollten Sie sie deklarieren noexcept. Beachten Sie jedoch, dass der Compiler keine Verstöße erkennen kann, noexceptwenn sich Ihre Implementierung ändert.

  1. In welchen Situationen sollte ich bei der Verwendung von noexcept vorsichtiger sein und in welchen Situationen kann ich mit dem implizierten noexcept (false) davonkommen?

Es gibt vier Funktionsklassen, auf die Sie sich konzentrieren sollten, da sie wahrscheinlich den größten Einfluss haben:

  1. Verschiebungsoperationen (Verschiebungszuweisungsoperator und Verschiebungskonstruktoren)
  2. Swap-Operationen
  3. Speicherfreigaben (Operator löschen, Operator löschen [])
  4. Destruktoren (obwohl diese implizit sind, es noexcept(true)sei denn, Sie machen sie noexcept(false))

Diese Funktionen sollten im Allgemeinen vorhanden sein noexcept, und es ist sehr wahrscheinlich, dass Bibliotheksimplementierungen die noexceptEigenschaft verwenden können. std::vectorKann beispielsweise nicht werfende Bewegungsoperationen verwenden, ohne starke Ausnahmegarantien zu opfern. Andernfalls muss auf das Kopieren von Elementen zurückgegriffen werden (wie in C ++ 98).

Diese Art der Optimierung erfolgt auf algorithmischer Ebene und basiert nicht auf Compiler-Optimierungen. Dies kann erhebliche Auswirkungen haben, insbesondere wenn das Kopieren der Elemente teuer ist.

  1. Wann kann ich realistisch mit einer Leistungsverbesserung nach Verwendung von noexcept rechnen? Geben Sie insbesondere ein Beispiel für Code an, für den ein C ++ - Compiler nach dem Hinzufügen von noexcept besseren Maschinencode generieren kann.

Der Vorteil noexceptgegenüber keiner Ausnahmespezifikation oder throw()ist, dass der Standard den Compilern mehr Freiheit beim Abwickeln von Stapeln ermöglicht. Selbst in diesem throw()Fall muss der Compiler den Stapel vollständig abwickeln (und dies in der exakten umgekehrten Reihenfolge der Objektkonstruktionen).

In diesem noexceptFall ist dies jedoch nicht erforderlich. Es ist nicht erforderlich, dass der Stapel abgewickelt werden muss (der Compiler darf dies jedoch weiterhin tun). Diese Freiheit ermöglicht eine weitere Codeoptimierung, da dadurch der Aufwand verringert wird, den Stapel immer wieder abwickeln zu können.

Die verwandte Frage zu noexcept, Abwickeln des Stapels und Leistung geht näher auf den Overhead ein, wenn das Abwickeln des Stapels erforderlich ist.

Ich empfehle auch das Scott Meyers-Buch "Effective Modern C ++", "Punkt 14: Deklarieren Sie Funktionen, die keine Ausnahmen sind, wenn sie keine Ausnahmen ausgeben", um sie weiter zu lesen.

Philipp Claßen
quelle
Dennoch wäre es viel sinnvoller, wenn Ausnahmen in C ++ wie in Java implementiert würden, wo Sie eine Methode markieren, die möglicherweise mit einem throwsSchlüsselwort anstelle eines noexceptNegativs ausgelöst wird. Ich kann einfach nicht einige C ++ Design-Optionen bekommen ...
Designoptionen
Sie nannten es, noexceptweil throwes bereits vergeben war. Einfach ausgedrückt throwkann fast verwendet werden so verwendet werden, wie Sie es erwähnen, außer dass sie das Design verpfuscht haben, so dass es fast unbrauchbar wurde - sogar schädlich. Aber wir bleiben jetzt dabei, da das Entfernen eine bahnbrechende Änderung mit geringem Nutzen wäre. So noexceptist es im Grunde throw_v2.
AnorZaken
Wie ist das thrownicht sinnvoll?
Neugieriger
@curiousguy "throw" selbst (zum Auslösen von Ausnahmen) ist nützlich, aber "throw" als Ausnahmespezifizierer wurde veraltet und in C ++ 17 sogar entfernt. Aus Gründen, warum Ausnahmespezifizierer nicht nützlich sind, siehe diese Frage: stackoverflow.com/questions/88573/…
Philipp Claßen
1
@ PhilippClaßen Der throw()Ausnahmespezifizierer bot nicht die gleiche Garantie wie nothrow?
Neugieriger
17

Es gibt viele Beispiele für Funktionen, von denen ich weiß, dass sie niemals ausgelöst werden, für die der Compiler dies jedoch nicht selbst bestimmen kann. Sollte ich in all diesen Fällen keine Ausnahme an die Funktionsdeklaration anhängen?

Wenn Sie sagen "Ich weiß, dass [sie] niemals werfen werden", meinen Sie, indem Sie die Implementierung der Funktion untersuchen, von der Sie wissen, dass die Funktion nicht werfen wird. Ich denke, dieser Ansatz ist von innen nach außen.

Es ist besser zu überlegen, ob eine Funktion Ausnahmen auslösen kann, um Teil der zu sein Entwurfs der Funktion zu sein: so wichtig wie die Argumentliste und ob eine Methode ein Mutator ist (... const). Die Erklärung, dass "diese Funktion niemals Ausnahmen auslöst", ist eine Einschränkung für die Implementierung. Das Weglassen bedeutet nicht, dass die Funktion Ausnahmen auslösen kann. Dies bedeutet, dass die aktuelle Version der Funktion und alle zukünftigen Versionen Ausnahmen auslösen können. Dies ist eine Einschränkung, die die Implementierung erschwert. Einige Methoden müssen jedoch die Einschränkung haben, praktisch nützlich zu sein. Am wichtigsten ist jedoch, dass sie von Destruktoren aufgerufen werden können, aber auch für die Implementierung von "Rollback" -Code in Methoden, die die Garantie für starke Ausnahmen bieten.

Raedwald
quelle
Dies ist bei weitem die beste Antwort. Sie geben den Benutzern Ihrer Methode eine Garantie. Dies ist eine andere Möglichkeit zu sagen, dass Sie Ihre Implementierung für immer einschränken (ohne Änderungen). Danke für die aufschlussreiche Perspektive.
AnorZaken
Siehe auch eine verwandte Java-Frage .
Raedwald