Das noexcept
Schlü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, noexcept
scheint 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
.
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
noexcept
in all diesen Fällen an die Funktionsdeklaration anhängen ?Überlegen zu müssen, ob ich
noexcept
nach 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 seinnoexcept
und in welchen Situationen kann ich mit dem Implizierten davonkommennoexcept(false)
?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 kannnoexcept
.Persönlich ist mir wichtig
noexcept
, dass der Compiler mehr Freiheit hat, bestimmte Arten von Optimierungen sicher anzuwenden. Nutzen moderne Compiler diese Vorteilenoexcept
? Wenn nicht, kann ich erwarten, dass einige von ihnen dies in naher Zukunft tun?
move_if_nothrow
(oder whatchamacallit), wird eine Leistungsverbesserung feststellen, wenn es keine Ausnahmeverschiebung gibt.move_if_noexcept
.Antworten:
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.
Verwenden Sie es dann, wenn es offensichtlich ist, dass die Funktion niemals ausgelöst wird.
Es scheint, dass die größten Optimierungsgewinne aus Benutzeroptimierungen resultieren, nicht aus Compileroptimierungen, da die Möglichkeit besteht, diese zu überprüfen
noexcept
und 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
noexcept
in den großen vier (Konstruktoren, Zuweisungen, nicht Destruktoren, wie sie bereits sindnoexcept
) führt wahrscheinlich zu den besten Verbesserungen, danoexcept
Überprüfungen im Vorlagencode, z. B. instd
Containern, häufig vorkommen. Zum Beispielstd::vector
wird Ihre Klassen bewegen verwenden , wenn es markiert istnoexcept
(oder der Compiler kann es anders ableiten).quelle
std::terminate
Trick gehorcht immer noch dem Zero-Cost-Modell. Das heißt, es kommt einfach so vor, dass der Befehlsbereich innerhalb dernoexcept
Funktionen dem Aufruf zugeordnet wird,std::terminate
wennthrow
er anstelle des Stapelabwicklers verwendet wird. Ich bezweifle daher, dass es mehr Overhead hat als die reguläre Ausnahmeverfolgung.noexcept
garantiert dies.noexcept
Funktion ausgelöst wird,std::terminate
wird 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.noexcept
ist 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 ...Wie ich heutzutage immer wieder wiederhole: Semantik zuerst .
Hinzufügen
noexcept
,noexcept(true)
undnoexcept(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 vonconst
: 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
noexcept
oder nicht, und die Standardbibliotheksimplementierung selbst verwendet diese Merkmale, umnoexcept
Operationen 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
noexcept
wirft,std::terminate
wird aufgerufen.Diese Semantik wurde aus zwei Gründen gewählt:
noexcept
auch wenn Abhängigkeiten es nicht bereits nutzen (Abwärtskompatibilität)noexcept
beim Aufrufen von Funktionen, die theoretisch auslösen können, aber für die angegebenen Argumente nicht erwartet werdenquelle
noexcept
Funktionen aufruft , nichts Besonderes tun muss, da eventuell auftretende Ausnahmen ausgelöst werden,terminate
bevor sie diese Ebene erreichen. Dies unterscheidet sich stark von der Notwendigkeit, einebad_alloc
Ausnahme zu behandeln und zu verbreiten .noexcept
eindeutig 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?noexcept
der 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.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:
Das Flussdiagramm für diese Funktion unterscheidet sich, wenn
bar
es beschriftet istnoexcept
(es gibt keine Möglichkeit für die Ausführung, zwischen dem Endebar
und der catch-Anweisung zu springen ). Bei der Bezeichnung alsnoexcept
ist 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 vonbar()
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:
noexcept
Der 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 kannnoexcept
(das Zuweisen von Speicher kann beispielsweise bad_alloc auslösen).quelle
x = 5
werfen kann. Wenn dieser Teil destry
Blocks irgendeinen Zweck erfüllen würde, würde die Argumentation nicht gelten.throw
Anweisungen suchen oder andere Dinge wienew
diese auslösen. Wenn der Compiler den Body nicht sehen kann, muss er sich auf das Vorhandensein oder Fehlen von verlassennoexcept
. 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.)throw()
in Pre-noexcept C ++noexcept
kann 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 durchstd::move_if_noexcept
. Zum Beispiel muss das Wachstum vonstd::vector
(z. B. wenn wir anrufenreserve
) eine starke Ausnahmesicherheitsgarantie bieten. Wenn es weiß, dassT
der Verschiebungskonstruktor nicht wirft, kann er einfach jedes Element verschieben. Andernfalls müssen alleT
s kopiert werden . Dies wurde in diesem Beitrag ausführlich beschrieben .quelle
noexcept
(falls zutreffend)! Implizit definierte Funktionen für Verschiebungselemente wurdennoexcept
automatisch hinzugefügt (falls zutreffend).Ähm, niemals? Ist nie eine Zeit? Noch nie.
noexcept
ist für Compiler- Leistungsoptimierungen genauso wieconst
für Compiler-Leistungsoptimierungen. Das heißt, fast nie.noexcept
wird 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. Sonoexcept
ist 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_noexcept
erkennen, ob der Verschiebungskonstruktor mit definiert ist,noexcept
und geben ein aconst&
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,
noexcept
wenn Sie der Meinung sind, dass dies tatsächlich nützlich ist . Einige Codes nehmen unterschiedliche Pfade, wennis_nothrow_constructible
dies für diesen Typ zutrifft. Wenn Sie Code verwenden, der dies ermöglicht, wenden Sie sich an dienoexcept
entsprechenden 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.
quelle
move_if_noexcept
wird 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_noexcept
die Kopie jedoch nicht ausführt. Ansonsten tolle Erklärung.noexcept
. So dass "nie" nicht wahr ist.std::vector
es 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.In Bjarnes Worten ( The C ++ Programming Language, 4. Ausgabe , Seite 366):
quelle
noexcept
jedes 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.noexcept
ist schwierig, da es Teil der Funktionsschnittstelle ist. Insbesondere wenn Sie eine Bibliothek schreiben, kann Ihr Client-Code von dernoexcept
Eigenschaft 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
noexcept
oder 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 wegzulassennoexcept
.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,noexcept
wenn sich Ihre Implementierung ändert.Es gibt vier Funktionsklassen, auf die Sie sich konzentrieren sollten, da sie wahrscheinlich den größten Einfluss haben:
noexcept(true)
sei denn, Sie machen sienoexcept(false)
)Diese Funktionen sollten im Allgemeinen vorhanden sein
noexcept
, und es ist sehr wahrscheinlich, dass Bibliotheksimplementierungen dienoexcept
Eigenschaft verwenden können.std::vector
Kann 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.
Der Vorteil
noexcept
gegenüber keiner Ausnahmespezifikation oderthrow()
ist, dass der Standard den Compilern mehr Freiheit beim Abwickeln von Stapeln ermöglicht. Selbst in diesemthrow()
Fall muss der Compiler den Stapel vollständig abwickeln (und dies in der exakten umgekehrten Reihenfolge der Objektkonstruktionen).In diesem
noexcept
Fall 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.
quelle
throws
Schlüsselwort anstelle einesnoexcept
Negativs ausgelöst wird. Ich kann einfach nicht einige C ++ Design-Optionen bekommen ...noexcept
weilthrow
es bereits vergeben war. Einfach ausgedrücktthrow
kann 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. Sonoexcept
ist es im Grundethrow_v2
.throw
nicht sinnvoll?throw()
Ausnahmespezifizierer bot nicht die gleiche Garantie wienothrow
?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.quelle