C ++ 17 führt das [[nodiscard]]
Attribut ein, mit dem Programmierer Funktionen so kennzeichnen können, dass der Compiler eine Warnung ausgibt, wenn das zurückgegebene Objekt von einem Aufrufer verworfen wird. Das gleiche Attribut kann einem gesamten Klassentyp hinzugefügt werden.
Ich habe im ursprünglichen Vorschlag über die Motivation für dieses Feature gelesen und weiß, dass C ++ 20 das Attribut zu Standardfunktionen wie hinzufügen wird std::vector::empty
, deren Namen keine eindeutige Bedeutung in Bezug auf den Rückgabewert vermitteln.
Es ist eine coole und nützliche Funktion. In der Tat scheint es fast zu nützlich. Überall, wo ich darüber lese [[nodiscard]]
, diskutieren die Leute darüber, als würden Sie es nur einigen ausgewählten Funktionen oder Typen hinzufügen und den Rest vergessen. Aber warum sollte ein nicht verwerfbarer Wert ein Sonderfall sein, insbesondere beim Schreiben von neuem Code? Ist ein verworfener Rückgabewert nicht in der Regel ein Fehler oder zumindest eine Verschwendung von Ressourcen?
Und gehört es nicht zu den Entwurfsprinzipien von C ++, dass der Compiler so viele Fehler wie möglich abfängt?
Wenn ja, warum fügen [[nodiscard]]
Sie dann nicht zu fast jeder einzelnen Nichtfunktion void
und zu fast jeder einzelnen Klassenart Ihren eigenen Code hinzu , der kein Legacy-Code ist ?
Ich habe versucht, das in meinem eigenen Code zu tun, und es funktioniert gut, außer es ist so schrecklich ausführlich, dass es sich wie Java anfühlt. Es erscheint viel natürlicher, Compiler standardmäßig vor verworfenen Rückgabewerten warnen zu lassen , mit Ausnahme der wenigen anderen Fälle, in denen Sie Ihre Absicht markieren [*] .
Da ich in Standardvorschlägen, Blogeinträgen, Stack Overflow-Fragen oder irgendwo anders im Internet keine Diskussionen über diese Möglichkeit gesehen habe, muss mir etwas fehlen.
Warum macht eine solche Mechanik in neuem C ++ - Code keinen Sinn? Ist Ausführlichkeit der einzige Grund, nicht [[nodiscard]]
fast überall zu verwenden?
[*] Theoretisch könnten Sie [[maydiscard]]
stattdessen so etwas wie ein Attribut haben, das auch rückwirkend zu Funktionen wie printf
in Standardbibliotheksimplementierungen hinzugefügt werden kann .
operator =
zum Beispiel. Undstd::map::insert
.const
können eine ansonsten "einfache" Klasse (oder vielmehr "einfaches altes Datenobjekt") in C ++ erheblich aufblähen lassen .std::vector
oderstd::unique_ptr
, von denen Sie nur Datenelemente in Ihrer Klassendefinition benötigen. Ich habe mit beiden Sprachen gearbeitet; Java ist eine okayische Sprache, aber ausführlicher.Antworten:
Verwenden Sie in neuem Code, der nicht mit älteren Standards kompatibel sein muss, dieses Attribut, wo immer es sinnvoll ist. Aber für C ++,
[[nodiscard]]
macht einen schlechten Standard. Du schlägst vor:Dies würde plötzlich dazu führen, dass vorhandener, korrekter Code viele Warnungen ausgibt. Während eine solche Änderung technisch als abwärtskompatibel angesehen werden könnte, da vorhandener Code immer noch erfolgreich kompiliert wird, wäre dies in der Praxis eine enorme Änderung der Semantik.
Entwurfsentscheidungen für eine vorhandene, ausgereifte Sprache mit einer sehr großen Codebasis unterscheiden sich notwendigerweise von Entwurfsentscheidungen für eine vollständig neue Sprache. Wenn dies eine neue Sprache wäre, wäre eine Warnung standardmäßig sinnvoll. In der Sprache Nim müssen beispielsweise nicht benötigte Werte explizit verworfen werden. Dies ähnelt dem Umbrechen jeder Ausdrucksanweisung in C ++ mit einer Umwandlung
(void)(...)
.Ein
[[nodiscard]]
Attribut ist in zwei Fällen am nützlichsten:wenn eine Funktion keine Auswirkungen hat, über die Rückgabe eines bestimmten Ergebnisses hinaus, dh rein ist. Wenn das Ergebnis nicht verwendet wird, ist der Aufruf sicherlich unbrauchbar. Andererseits wäre es nicht falsch, das Ergebnis zu verwerfen.
ob der Rückgabewert überprüft werden muss, z. B. für eine C-ähnliche Schnittstelle, die Fehlercodes zurückgibt, anstatt sie auszulösen. Dies ist der primäre Anwendungsfall. Für idiomatisches C ++ wird das ziemlich selten sein.
Diese beiden Fälle hinterlassen einen riesigen Bereich unreiner Funktionen, die einen Wert zurückgeben, bei dem eine solche Warnung irreführend wäre. Zum Beispiel:
Stellen Sie sich einen Warteschlangendatentyp mit einer
.pop()
Methode vor, die ein Element entfernt und eine Kopie des entfernten Elements zurückgibt. Eine solche Methode ist oftmals zweckmäßig. In einigen Fällen möchten wir jedoch nur das Element entfernen, ohne eine Kopie zu erhalten. Das ist absolut legitim und eine Warnung wäre nicht hilfreich. Ein anderes Design (wiestd::vector
) teilt diese Verantwortlichkeiten auf, aber das hat andere Nachteile.Beachten Sie, dass in einigen Fällen eine Kopie angefertigt werden muss, sodass die Rücksendung der Kopie dank RVO kostenlos wäre.
Betrachten Sie fließende Schnittstellen, bei denen jede Operation das Objekt zurückgibt, damit weitere Operationen ausgeführt werden können. In C ++ ist der Operator zum Einfügen von Streams das häufigste Beispiel
<<
. Es wäre äußerst umständlich,[[nodiscard]]
jeder<<
Überladung ein Attribut hinzuzufügen .Diese Beispiele zeigen, dass das Kompilieren von idiomatischem C ++ - Code ohne Warnungen unter einer Sprache "C ++ 17 mit standardmäßigem NODISCARD" ziemlich mühsam wäre.
Beachten Sie, dass Ihr glänzender neuer C ++ 17-Code (in dem Sie diese Attribute verwenden können) möglicherweise weiterhin zusammen mit Bibliotheken kompiliert wird, die auf ältere C ++ - Standards abzielen. Diese Abwärtskompatibilität ist für das C / C ++ - Ökosystem von entscheidender Bedeutung. Wenn Sie also nodiscard als Standard festlegen, werden für typische, idiomatische Anwendungsfälle viele Warnungen ausgegeben, die Sie nicht beheben können, ohne den Quellcode der Bibliothek grundlegend zu ändern.
Das Problem hierbei ist wahrscheinlich nicht die Änderung der Semantik, sondern die Tatsache, dass die Funktionen jedes C ++ - Standards für einen Bereich pro Kompilierungseinheit gelten und nicht für einen Bereich pro Datei. Wenn sich ein zukünftiger C ++ - Standard von den Header-Dateien entfernt, wäre eine solche Änderung realistischer.
quelle
pop
oderoperator<<
werden diese explizit durch mein imaginäres[[maydiscard]]
Attribut gekennzeichnet, sodass keine Warnungen generiert werden. Wollen Sie damit sagen, dass solche Funktionen im Allgemeinen viel häufiger sind, als ich glauben möchte, oder im Allgemeinen viel häufiger als in meinen eigenen Codebasen? Ihr erster Absatz über vorhandenen Code ist sicherlich richtig, aber ich frage mich immer noch, ob derzeit jemand anderen seinen gesamten neuen Code mit[[nodiscard]]
und wenn nicht, warum nicht peppt .returns_an_int() = 0
, warum sollte dasreturns_a_str() = ""
funktionieren? C ++ 11 zeigte, dass dies tatsächlich eine schreckliche Idee war, da dieser Code nun alle Bewegungen untersagte, da die Werte const waren. Ich denke, die richtige Lösung für diese Lektion ist "Es liegt nicht an Ihnen, was Ihre Anrufer mit Ihrem Ergebnis machen". Das Ignorieren des Ergebnisses ist eine absolut gültige Sache, die Anrufer möglicherweise tun möchten. Wenn Sie nicht wissen, dass es falsch ist (ein sehr hoher Balken), sollten Sie es nicht blockieren.empty()
ist auch deshalb sinnvoll, weil der Name nicht eindeutig ist: Ist es ein Prädikatis_empty()
oder ein Mutatorclear()
? Sie würden normalerweise nicht erwarten, dass ein solcher Mutator irgendetwas zurückgibt, daher kann eine Warnung über einen Rückgabewert den Programmierer auf ein Problem aufmerksam machen. Mit einem klareren Namen wäre dieses Attribut nicht erforderlich.[[pure]]
Attribut geben, und das sollte implizieren[[nodiscard]]
. Auf diese Weise ist klar, warum dieses Ergebnis nicht verworfen werden sollte. Aber abgesehen von reinen Funktionen kann ich mir keine andere Klasse von Funktionen vorstellen, die dieses Verhalten haben sollten. Und die meisten Funktionen sind nicht rein, daher denke ich nicht, dass NODISCARD als Standard die richtige Wahl ist.Gründe, warum ich nicht
[[nodiscard]]
fast überall wäre:Wenn Sie jetzt die Standardeinstellung festlegen, werden alle Bibliotheksentwickler gezwungen, alle Benutzer dazu zu zwingen, die Rückgabewerte nicht zu verwerfen. Das wäre schrecklich. Oder du zwingst sie,
[[maydiscard]]
unzählige Funktionen hinzuzufügen , was auch irgendwie schrecklich ist.quelle
Als Beispiel: Operator << hat einen Rückgabewert, der je nach Aufruf entweder absolut benötigt oder absolut unbrauchbar ist. (std :: cout << x << y, der erste wird benötigt, weil er den Stream zurückgibt, der zweite ist überhaupt nicht von Nutzen). Vergleichen Sie nun mit printf, wo jeder den Rückgabewert verwirft, der für die Fehlerprüfung zur Verfügung steht, aber dann hat der Operator << keine Fehlerprüfung, so dass er überhaupt nicht so nützlich gewesen sein kann. Nodiscard wäre also in beiden Fällen nur schädlich.
quelle
[[nodiscard]]
überall zu verwenden?".