2015-03-18 10 views
14

In unserem Code-Basis haben wir viele Konstruktionen wie folgt aus:Sicherer Zeiger Dereferenzierung in C++

auto* pObj = getObjectThatMayVeryRarelyBeNull(); 
if (!pObj) throw std::runtime_error("Ooops!"); 
// Use pObj->(...) 

In 99,99% der Fälle diese Prüfung ausgelöst wird, nicht. Ich denke an die folgende Lösung:

auto& obj = deref_or_throw(getObjectThatMayVeryRarelyBeNull()); 
// Use obj.(...) 

Wo deref_or_throw wie folgt erklärt:

template<class T> T& deref_or_throw(T* p) { 
    if (p == nullptr) { throw std::invalid_argument("Argument is null!"); } 
    return *p; 
} 

Dieser Code viel klarer ist und funktioniert wie ich brauche.

Die Frage ist: erfinde ich das Rad neu? Gibt es eine verwandte Lösung in Standard oder Boost? Oder haben Sie Kommentare zur Lösung?

PS. Verwandte Fragen (ohne zufriedenstellende Antwort): Is there a C++ equivalent of a NullPointerException

+0

Gibt es überhaupt einen Code, der sich tatsächlich mit dem 'nullptr'-Fall befasst? Z.B. alles wie 'if (! obj) {std :: cout <<" Null, aber immer noch okay, mach weiter mit etwas Bedeutungsvollem \ n "; } else {std :: cout << "Non-Null, etwas anderes tun \ n"; } '. Oder ist der 'nullptr'-Fall immer ein" exit, weil das falsch ist "-Pfad? – stefan

+0

Dieser Code existiert, ist aber selten. In den meisten Fällen können wir einfach nicht fortfahren. Nichts, was wir tun können. Die ganze Routine ist bedeutungslos. Also vielleicht Existenz dieses Objekts ist eine _precondition_? Und ich muss es überprüfen und dann den Zeiger manuell dereferenzieren, ohne sich Sorgen zu machen? – Mikhail

+1

Genau das müssen Sie für sich selbst klären, imho. Wenn es eine Vorbedingung ist, ist vielleicht eine 'Behauptung' besser geeignet. Ansonsten ist dies eindeutig eine Ausnahme (0,01% ist ziemlich außergewöhnlich), so dass eine "Ausnahme" angebracht ist. Afaik, in einer Bibliothek gibt es so etwas nicht. – stefan

Antwort

4

Es gibt zwei Möglichkeiten, mit dem Problem "seltener Nullzeiger" umzugehen. Erstens ist es die vorgeschlagene Ausnahmelösung. Dafür ist die Methode deref_or_throw eine gute Sache, obwohl ich lieber eine runtime_error, als eine invalid_argument Ausnahme werfen würde. Sie können auch erwägen, es NullPointerException zu nennen, wenn Sie das bevorzugen.

Wenn jedoch der ptr != nullptr Fall tatsächlich eine Vorbedingung für den Algorithmus ist, sollten Sie Ihr Bestes versuchen, um 100% Sicherheit zu erreichen, wenn Sie diesen Nicht-Null-Fall haben. Eine assert ist in diesem Fall das Richtige.

Vorteile von assert:

  • Keine Kosten zur Laufzeit im Release-Modus
  • macht es glasklar in den Entwickler, dass er dies geschieht nie verantwortlich ist.

Nachteile assert:

  • Potential nicht definiertes Verhalten im Release-Modus

Sie sollten auch prüfen, ein Verfahren zu schreiben getObjectThatMayNeverBeNullButDoingTheSameAsGetObjectThatMayVeryRarelyBeNull(): Ein Verfahren, das einen Nicht-Null-Zeiger zurück ist garantiert ein Objekt, das ansonsten völlig Ihrer ursprünglichen Methode entspricht. Wenn Sie jedoch die Extrameile gehen können, würde ich dringend empfehlen, einen rohen Zeiger sowieso nicht zurückzugeben. Embrace C++ 11 und Rückgabe von Objekten nach Wert :-)

+0

Ich möchte Assertions haben, die in Release nicht gelöscht werden. Und ich möchte zwischen Behauptungen und Voraussetzungen unterscheiden. Irgendwelche guten Links zu diesem Thema? – Mikhail

+0

Behauptungen sind Vorbedingungen (oder zumindest die nächste Sache). Aber nichts hindert Sie daran, sowohl die Behauptung als auch die Ausnahme zu verwenden. :) – stefan

+0

Ja, Lösungen sind so offensichtlich, dass ich sicher bin, dass jemand einen guten Job gemacht hat und einen konsistenten und allgemeinen Rahmen dafür hat. Ich kenne jedoch keine. Boost.assert hat beispielsweise keine Vorbedingungen. – Mikhail

1

Sie können und sollten wahrscheinlich mit null Fällen im Hinterkopf entwerfen. Zum Beispiel, in Ihrer Problemdomäne, wann ist es sinnvoll, dass Klassenmethoden null zurückgeben? Wann macht es Sinn, Funktionen mit null Argumenten aufzurufen?

Grob gesagt, kann es vorteilhaft sein, null Referenzen aus Code wann immer möglich zu entfernen. Mit anderen Worten, Sie können die Invariante so programmieren, dass "dies hier nicht Null ist ... oder dort". Dies hat viele Vorteile. Sie müssen Code nicht durch Umhüllungsmethoden in deref_or_throw verschleiern. Sie können mehr semantische Bedeutung durch Code erreichen, weil, wie oft sind die Dinge in der realen Welt null? Ihr Code ist möglicherweise besser lesbar, wenn Sie Ausnahmen anstelle von null Werten verwenden, um Fehler anzuzeigen. Und schließlich reduzieren Sie die Notwendigkeit zur Fehlerprüfung und reduzieren auch das Risiko von Laufzeitfehlern (die gefürchtete Nullzeiger-Dereferenzierung).

Wenn Ihr System nicht mit null Fällen konzipiert wurde, würde ich sagen, dass es am besten ist, es allein zu lassen und nicht verrückt, alles mit deref_or_throw wickeln. Vielleicht agiles Vorgehen. Untersuchen Sie beim Codieren die Verträge, die Ihre Klassenobjekte als Dienste anbieten. Wie oft können diese Verträge vernünftigerweise erwartet werden, dass sie null zurückgeben? Was ist der semantische Wert von null in diesen Fällen?

Sie könnten wahrscheinlich die Klassen in Ihrem System identifizieren, von denen erwartet werden kann, dass sie null zurückgeben. Für diese Klassen kann null eine gültige Geschäftslogik sein und keine Randfälle, die auf Implementierungsfehler schließen lassen. Für die Klassen, die voraussichtlich null zurückgeben, kann die zusätzliche Überprüfung die Sicherheit wert sein. In diesem Sinne fühlt sich das Überprüfen nach null mehr wie Geschäftslogik eher als Implementierungsdetails auf niedriger Ebene an. Auf der anderen Seite könnte die systematische Umhüllung aller Zeiger-Referenzen in deref_or_throw eine Heilung sein, die ihr eigenes Gift ist.

+0

Die Funktion 'getObjectThatMayVeryRarelyBeNull()' sollte die Möglichkeit haben, 'null' zurückzugeben. Es ist selten, aber es ist möglich. Aber da es selten ist, erwarten die meisten Routinen (außer einigen speziellen), dass sie ein Objekt zurückgeben. Sonst sind sie einfach bedeutungslos. – Mikhail

+0

Ich wäre versucht, Null als gültigen Rückgabewert zu entfernen, und vielleicht eine andere Option, um diesen sehr seltenen Fall anzuzeigen. Ohne Ihren Anwendungsfall zu kennen, kann ich nicht genau darüber spekulieren, was das wäre. Es scheint so, als ob Ihre Klasse ein "überraschendes" Ergebnis zurückgeben kann, das nicht alle Angehörigen richtig handhaben würden, so dass es in der Tat gefährlich erscheint, dort null zurückzugeben. Und natürlich sollten zukünftige Leser Ihres Codes nicht die Subtilität auffassen müssen, dass 'getObjectThatMayVeryRarelyBeNull() 'null 0.01% der Zeit zurückgibt. –

+0

Das ist in der Tat eine knifflige Designfrage und deshalb frage ich nicht danach :) Ich schränke den Bereich der möglichen Lösungen so ein, dass die Schnittstelle von 'getObjectThatMayVeryRarelyBeNull()' nicht geändert werden kann. – Mikhail

Verwandte Themen