Sehr oft haben Sie eine Funktion, die für gegebene Argumente kein gültiges Ergebnis erzeugen kann oder einige Aufgaben nicht ausführen kann. Abgesehen von Ausnahmen, die in der C / C ++ - Welt nicht so häufig verwendet werden, gibt es grundsätzlich zwei Schulen, die ungültige Ergebnisse melden.
Der erste Ansatz mischt gültige Rückgabewerte mit einem Wert, der nicht zum Codomain einer Funktion gehört (sehr oft -1) und einen Fehler anzeigt
%Vor%Der zweite Ansatz besteht darin, einen Funktionsstatus zurückzugeben und das Ergebnis innerhalb einer Referenz zu übergeben
%Vor%Welchen Weg bevorzugen Sie und warum? Verursachen zusätzliche Parameter in der zweiten Methode einen bemerkenswerten Leistungsaufwand?
Ignorieren Sie keine Ausnahmen für außergewöhnliche und unerwartete Fehler.
Wenn Sie jedoch nur Ihre Punkte beantworten, ist die Frage letztlich subjektiv. Das Hauptproblem besteht darin, zu überlegen, was für Ihre Kunden einfacher zu handhaben ist, während Sie sie ruhig an die Fehlerbedingungen erinnern. Meiner Meinung nach ist dies fast immer der "Rückgabe eines Statuscodes, und lege den Wert in eine separate Referenz", aber dies ist völlig eine persönliche Ansicht. Meine Argumente dafür ...
Argumente für die Rückgabe des gemischten Fehlercodes / Wertmodells könnten Einfachheit sein - keine zusätzlichen Variablen, die umher fließen. Aber für mich sind die Gefahren schlimmer als die begrenzten Gewinne - man kann leicht vergessen, die Fehlercodes zu überprüfen. Dies ist ein Argument für Ausnahmen - Sie können buchstäblich nicht vergessen, mit ihnen umzugehen (Ihr Programm wird ausgelöscht, wenn Sie das nicht tun).
boost optional ist eine brillante Technik. Ein Beispiel wird helfen.
Angenommen, Sie haben eine Funktion, die ein Double zurückgibt, und Sie möchten dies angeben ein Fehler, wenn das nicht berechnet werden kann.
%Vor%was zu tun ist, wenn b 0 ist;
%Vor%benutze es wie unten.
%Vor%Die Idee spezieller Rückgabewerte fällt vollständig aus, wenn Sie mit der Verwendung von Vorlagen beginnen. Überlegen Sie:
%Vor%Es gibt keinen offensichtlichen Sonderwert, den wir in diesem Fall zurückgeben können. Daher ist das Auslösen einer Ausnahme der einzige Weg. Das Zurückgeben boolescher Typen, die überprüft werden müssen, und das Zurückgeben der wirklich interessanten Werte durch Referenz führt zu einem horrenden Codierungsstil.
Eine ganze Reihe von Büchern usw. empfehlen dringend die zweite, also mischen Sie keine Rollen und zwingen den Rückgabewert dazu, zwei völlig unabhängige Informationen zu tragen.
Während ich mit dieser Vorstellung sympathisiere, finde ich, dass das erste typischerweise in der Praxis besser funktioniert. Für einen offensichtlichen Punkt, im ersten Fall können Sie die Zuordnung zu einer beliebigen Anzahl von Empfängern verketten, aber in der zweiten, wenn Sie das Ergebnis mehr als einem Empfänger zuordnen möchten / müssen, müssen Sie den Aufruf durchführen und dann separat ausführen eine zweite Aufgabe. I.e.,
%Vor%gegen:
%Vor%oder:
%Vor%Der Beweis des Puddings ist im Essen davon. Microsofts COM-Funktionen (für ein Beispiel) wählten ausschließlich die letztere Form. IMO, es liegt hauptsächlich an dieser Entscheidung allein, dass im Wesentlichen alle Code, der die native COM-API direkt verwendet, hässlich und fast nicht lesbar ist. Die involvierten Konzepte sind nicht besonders schwierig, aber der Stil der Schnittstelle macht praktisch jeden Code, der einfacher Code sein sollte, in eine fast unlesbare Unordnung.
Ausnahmebehandlung ist normalerweise ein besserer Weg, um mit Dingen umzugehen als mit beiden. Es hat drei spezifische Effekte, die alle sehr gut sind. Erstens hält es die Mainstream-Logik davon ab, mit der Fehlerbehandlung verschmutzt zu werden, so dass die wirkliche Absicht des Codes viel klarer ist. Zweitens entkoppelt es den Fehler handling von der Fehler Erkennung . Code, der ein Problem erkennt, befindet sich oft in einer schlechten Position, um diesen Fehler sehr gut zu behandeln . Drittens ist es im Gegensatz zu jeder Form der Rückgabe eines Fehlers im Wesentlichen unmöglich , eine ausgelöste Ausnahme einfach zu ignorieren. Bei den Return-Codes gibt es eine fast konstante Versuchung (der Programmierer allzu oft erliegen), einfach nur Erfolg zu haben und gar kein Problem zu machen - zumal der Programmierer nicht wirklich weiß, wie er mit dem Fehler umgehen soll Teil des Codes sowieso, und ist sich bewusst, dass, selbst wenn er es fängt und einen Fehlercode von seiner Funktion zurückgibt, Chancen gut sind, dass es sowieso ignoriert wird.
In C ist eine der gebräuchlichsten Techniken, die ich gesehen habe, die, dass eine Funktion bei einem Erfolg null zurückgibt (normalerweise ein Fehlercode). Wenn die Funktion Daten an den Aufrufer zurückgeben muss, geschieht dies über einen Zeiger, der als Funktionsargument übergeben wird. Dies kann auch dazu führen, dass Funktionen, die mehrere Daten an den Benutzer zurückgeben, einfacher zu verwenden sind (statt einige Daten über einen Rückgabewert und einige über einen Zeiger zurückzugeben).
Eine weitere C-Technik, die ich sehe, ist, dass bei Erfolg 0 zurückgegeben wird, und bei Fehler wird -1 zurückgegeben und errno
wird gesetzt, um den Fehler anzuzeigen.
Die Techniken, die Sie vorgestellt haben, haben Vor- und Nachteile, so dass es immer (zumindest teilweise) subjektiv ist, zu entscheiden, welcher der "besten" ist. Allerdings kann ich das ohne Vorbehalte sagen: Die beste Technik ist die Technik, die im gesamten Programm konsistent ist . Die Verwendung verschiedener Stile von Fehlerberichtscodes in verschiedenen Teilen eines Programms kann schnell zu einem Albtraum für die Wartung und das Debugging werden.
Es sollte nicht viel, wenn überhaupt, Leistungsunterschied zwischen den beiden geben. Die Auswahl hängt von der jeweiligen Verwendung ab. Sie können die erste nicht verwenden, wenn kein geeigneter ungültiger Wert vorhanden ist.
Wenn Sie C ++ verwenden, gibt es viel mehr Möglichkeiten als diese beiden, einschließlich Ausnahmen und Verwendung von etwas wie boost :: optional als Rückgabewert.
C verwendet traditionell den ersten Ansatz der Codierung von magischen Werten in gültigen Ergebnissen - deshalb erhalten Sie lustige Sachen wie strcmp (), die false (= 0) auf eine Übereinstimmung zurückgeben.
Neuere sichere Versionen vieler Standardbibliotheksfunktionen verwenden den zweiten Ansatz, bei dem explizit ein Status zurückgegeben wird.
Und keine Ausnahmen sind hier keine Alternative. Ausnahmen sind für außergewöhnliche Umstände, mit denen der Code möglicherweise nicht umgehen kann - Sie verursachen keine Ausnahme für eine Zeichenfolge, die nicht in strcmp () übereinstimmt
Es ist nicht immer möglich, aber unabhängig davon, welche Methode zur Fehlerberichterstattung Sie verwenden, sollten Sie nach Möglichkeit eine Funktion so entwickeln, dass keine Fehlerfälle auftreten. Wenn dies nicht möglich ist, minimieren Sie die möglichen Fehlerbedingungen . Einige Beispiele:
Statt einen Dateinamen tief in vielen Funktionsaufrufen zu übergeben, könnten Sie Ihr Programm so gestalten, dass der Aufrufer die Datei öffnet und den% desc_de% - oder Dateideskriptor übergibt. Auf diese Weise werden Prüfungen auf "Datei konnte nicht geöffnet werden" entfernt und bei jedem Schritt an den Aufrufer gemeldet.
Wenn es eine kostengünstige Möglichkeit gibt, die Menge an Speicher zu prüfen, die eine Funktion für die Datenstrukturen reservieren muss, die sie erstellen und zurückgeben wird, stellen Sie eine Funktion zur Verfügung, die diesen Betrag zurückgibt und hat der Anrufer reserviert den Speicher. In einigen Fällen kann dies dem Aufrufer ermöglichen, den Stapel einfach zu verwenden, wodurch die Speicherfragmentierung erheblich reduziert und Sperren in FILE *
vermieden werden.
Wenn eine Funktion eine Aufgabe ausführt, für die Ihre Implementierung viel Arbeitsraum benötigt, fragen Sie, ob es einen alternativen (möglicherweise langsameren) Algorithmus mit malloc
Speicherplatzanforderungen gibt. Wenn die Leistung nicht kritisch ist, verwenden Sie einfach den Algorithmus O(1)
space. Andernfalls implementieren Sie einen Fallback Fall, um es zu verwenden, wenn die Zuweisung fehlschlägt.
Dies sind nur ein paar Ideen, aber die Anwendung des gleichen Prinzips kann die Anzahl der Fehlerbedingungen reduzieren, mit denen Sie umgehen müssen, und sich über mehrere Aufrufstufen verbreiten.
Für C ++ bevorzuge ich eine Template-Lösung, die die Fugheit der Out-Parameter und der Fugheit von "magischen Zahlen" in kombinierten Antworten / Return-Codes verhindert. Ich habe dies erklärt, während beantwortet eine andere Frage . Schau es dir an.
Für C finde ich die fugly out Parameter weniger beleidigend als fugy "magische Zahlen".
Sie haben eine Methode verpasst: Zurückgeben einer Fehleranzeige und eines zusätzlichen Aufrufs, um die Details des Fehlers zu erhalten.
Es gibt eine Menge zu sagen.
Beispiel:
%Vor%Bearbeiten
Diese Antwort hat einiges an Kontroversen und Downvoting hervorgebracht. Um ehrlich zu sein, bin ich von den abweichenden Argumenten absolut nicht überzeugt. Die Trennung von ob ein Aufruf von warum fehlgeschlagen ist, hat sich als wirklich gute Idee erwiesen. Kombinieren Sie die beiden Kräfte in das folgende Muster:
%Vor%Vergleichen Sie dies mit:
%Vor%(Die GetLastError-Version ist konsistent mit der Funktionsweise der Windows-API allgemein . Die Version, die den Code direkt zurückgibt, funktioniert jedoch so, weil die Registrierungs-API diesem Standard nicht folgt.)
In jedem Fall würde ich vorschlagen, dass das Fehler zurückkehrende Muster es allzu leicht macht, warum die Funktion zu vergessen, was zu Code wie folgt führt:
%Vor%Bearbeiten
Mit Blick auf Rs Anfrage habe ich ein Szenario gefunden, in dem es tatsächlich befriedigt werden kann.
Für eine allgemeine C-style-API, wie die Windows SDK-Funktionen, die ich in meinen Beispielen verwendet habe, gibt es keinen nicht-globalen Kontext, in dem sich Fehlercodes befinden. Stattdessen haben wir keine gute Alternative zur Verwendung eine globale TLV, die nach einem Fehler überprüft werden kann.
Wenn wir jedoch das Thema erweitern, um Methoden für eine Klasse einzuschließen, ist die Situation anders. Es ist völlig in Ordnung, wenn eine Variable reg
eine Instanz der Klasse RegistryKey
für einen Aufruf von reg.Open
ist, um false
zurückzugeben, dann müssen wir reg.ErrorCode
aufrufen, um die Details abzurufen.
Ich glaube, dass dies die Forderung von R. erfüllt, dass der Fehlercode Teil eines Kontextes sein soll, da die Instanz den Kontext bereitstellt. Wenn wir anstelle einer RegistryKey
-Instanz eine statische Open
-Methode für RegistryKeyHelper
aufgerufen haben, müsste das Abrufen des Fehlercodes bei einem Fehler ebenfalls statisch sein, was bedeutet, dass es eine TLV sein müsste nicht ein ganz globaler. Die Klasse wäre im Gegensatz zu einer Instanz der Kontext.
In beiden Fällen bietet die Objektorientierung einen natürlichen Kontext zum Speichern von Fehlercodes. Abgesehen davon, dass es keinen natürlichen Kontext gibt, würde ich trotzdem auf einem globalen bestehen, anstatt zu versuchen, den Aufrufer dazu zu zwingen, einen Ausgabeparameter oder einen anderen künstlichen Kontext zu übergeben oder den Fehlercode direkt zurückzugeben.
Wenn Sie Referenzen und den bool-Typ haben, müssen Sie C ++ verwenden. In diesem Fall eine Ausnahme auslösen. Dafür sind sie da. Für eine allgemeine Desktop-Umgebung gibt es keinen Grund, Fehlercodes zu verwenden. Ich habe Argumente gegen Ausnahmen in einigen Umgebungen gesehen, wie doggy Sprache / Prozess Interop oder eng eingebettete Umgebung. Nehmen wir an, dass keines davon immer eine Ausnahme auslöst.
Nun, der erste wird entweder in C und C ++ kompilieren, also ist portabler Code in Ordnung. Der zweite, obwohl es "menschlicher lesbar" ist, Sie wissen nie wahrheitsgemäß, welchen Wert das Programm zurückgibt, indem Sie es wie im ersten Fall spezifizieren, gibt Ihnen mehr Kontrolle, das ist, was ich denke.
Ich bevorzuge Rückkehrcode für den Typ des aufgetretenen Fehlers. Dies hilft dem Aufrufer der API, geeignete Fehlerbehandlungsschritte zu ergreifen.
Betrachten Sie GLIB-APIs, die meistens den Fehlercode und die Fehlermeldung zusammen mit dem booleschen Rückgabewert zurückgeben.
Wenn Sie also eine negative Rückkehr zu einem Funktionsaufruf erhalten, können Sie den Kontext von der Variable GError überprüfen.
Ein Fehler in dem von Ihnen angegebenen zweiten Ansatz hilft dem Aufrufer nicht, die richtigen Maßnahmen zu ergreifen. Der andere Fall, wenn Ihre Dokumentation sehr klar ist. In anderen Fällen ist es jedoch ein Problem, den API-Aufruf zu verwenden.
Für eine "try" -Funktion, bei der ein "normaler" Fehlertyp vernünftigerweise erwartet wird, wie wäre es, entweder einen Standard-Rückgabewert oder einen Zeiger auf eine Funktion zu akzeptieren, die bestimmte Parameter in Bezug auf den Fehler akzeptiert und einen solchen Wert zurückgibt der erwartete Typ?
Abgesehen davon, dass Sie den richtigen Weg gewählt haben, welche dieser dummen Wege bevorzugen Sie?
Ich bevorzuge es, Ausnahmen zu verwenden, wenn ich C ++ benutze und einen Fehler ausgeben muss, und im Allgemeinen, wenn ich nicht alle aufrufenden Funktionen zwingen will, den Fehler zu erkennen und zu behandeln. Ich bevorzuge es, dumme Sonderwerte zu verwenden, wenn nur eine mögliche Fehlerbedingung vorliegt, und diese Bedingung bedeutet, dass der Anrufer nicht fortfahren kann und jeder denkbare Anrufer in der Lage ist, damit umzugehen. Das ist selten. Ich bevorzuge es, beim Modifizieren von altem Code dumme Out-Parameter zu verwenden, und aus irgendeinem Grund kann ich die Anzahl der Parameter ändern, aber nicht den Rückgabetyp ändern oder einen speziellen Wert identifizieren oder eine Exception werfen, was bisher noch nie geschehen ist.
Enthält zusätzliche Parameter in der zweite Methode bringt bemerkenswert Performance Overhead?
Ja! Zusätzliche Parameter führen zu einer Verlangsamung des Computers um mindestens 0 Nanosekunden. Am besten verwenden Sie das Schlüsselwort "no-overhead" für diesen Parameter. Es ist eine GCC-Erweiterung __attribute__((no-overhead))
, also YMMV.
Tags und Links c c++ return return-value