2015-03-18 19 views
14

w naszej bazie kodu mamy wiele konstrukcje tak:Bezpieczne wskaźnik dereferencing w C++

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

W 99,99% przypadków tego wyboru nie jest uruchomiony. Myślę o następującym roztworem:

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

Gdzie deref_or_throw jest zadeklarowana następująco:

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

Ten kod jest znacznie jaśniejsze i działa jak trzeba.

Pytanie brzmi: czy wymyślam nowe koło? Czy istnieje jakieś podobne rozwiązanie w standardzie lub boost? Czy masz jakieś komentarze na temat rozwiązania?

PS. Powiązane pytanie (bez satysfakcjonującej odpowiedzi): Is there a C++ equivalent of a NullPointerException

+0

Czy istnieje jakiś kod, który faktycznie zajmuje się sprawą 'nullptr'? Na przykład. coś w stylu 'if (! obj) {std :: cout <<" Null, ale nadal w porządku, kontynuując coś znaczącego \ n "; } else {std :: cout << "Non-Null, robiąc coś innego \ n"; } '. Czy też przypadek 'nullptr' jest zawsze" wyjściem, ponieważ jest to błędne "-ścieżka? – stefan

+0

Ten kod istnieje, ale jest rzadki. W większości przypadków po prostu nie możemy kontynuować. Nic nie możemy zrobić. Cała procedura jest bez znaczenia. Więc może istnienie tego obiektu to _warunek_? I muszę to sprawdzić, a następnie ręcznie usunąć zaznaczenie wskaźnika bez martwienia się? – Mikhail

+1

Dokładnie to właśnie trzeba wyjaśnić dla siebie, imho. Jeśli jest to warunek konieczny, być może "assert" jest lepszym rozwiązaniem. W przeciwnym razie jest to rzecz wyjątkowa (0,01% jest dość wyjątkowa), więc odpowiedni jest "wyjątek". Afaik, nie ma czegoś takiego w bibliotece. – stefan

Odpowiedz

4

Istnieją dwa sposoby rozwiązania problemu "rzadkiego przypadku wskaźnika zerowego". Po pierwsze, jest to proponowane rozwiązanie wyjątku. W tym celu dobrze jest zastosować metodę, ale wolałbym rzucić wyjątek runtime_error niż invalid_argument. Możesz także rozważyć nadanie jej nazwy NullPointerException, jeśli wolisz to.

Jeśli jednak przypadek ptr != nullptr jest w rzeczywistości warunkiem wstępnym do algorytmu, należy dołożyć wszelkich starań, aby osiągnąć 100% bezpieczeństwa w przypadku braku tej wartości zerowej. W tym przypadku odpowiednia jest opcja assert.

Zalety assert:

  • bez żadnych kosztów w czasie wykonywania w trybie zwolnienia
  • Sprawia to krystalicznie czysta do dewelopera, który jest odpowiedzialny za to, aby nigdy nie zdarzyć.

Wady assert:

  • Potencjał niezdefiniowane zachowanie w trybie zwolnienia

Należy również rozważyć napisanie metody getObjectThatMayNeverBeNullButDoingTheSameAsGetObjectThatMayVeryRarelyBeNull(): metoda, która na pewno zwróci niezerową wskaźnik obiekt, który w inny sposób jest całkowicie równoważny z oryginalną metodą. Jednakże, jeśli możesz pójść o krok dalej, zdecydowanie sugerowałbym nie zwracanie surowego wskaźnika. Embrace C++ 11 i zwracaj obiekty według wartości :-)

+0

Chciałbym mieć twierdzenia, które nie są usuwane w wersji. Chciałbym rozróżnić między twierdzeniami a warunkami wstępnymi. Jakieś dobre linki na ten temat? – Mikhail

+0

Asercje są warunkami wstępnymi (lub przynajmniej najbliższymi rzeczami). Ale nic nie powstrzyma cię od używania zarówno twierdzenia, jak i wyjątku. :) – stefan

+0

Tak, rozwiązania są tak oczywiste, że jestem pewien, że ktoś wykonał dobrą robotę i ma spójne i ogólne ramy. Ale nie znam żadnego. Boost.assert nie ma na przykład żadnych warunków wstępnych. – Mikhail

1

Można i prawdopodobnie należy zaprojektować z myślą o sprawach z null. Na przykład w domenie z problemem, kiedy ma sens, aby metody klas powróciły: null? Kiedy ma sens wywoływanie funkcji z argumentami null?

Ogólnie rzecz biorąc, może być korzystne usunięcie odniesień z kodu null z kodu, gdy tylko jest to możliwe. Innymi słowy, możesz zaprogramować na niezmiennym, że "nie będzie tutaj wartości zerowej ... ani tam". Ma to wiele zalet. Nie będziesz musiał zamazywać kodu przez zawijanie metod w deref_or_throw. Możesz osiągnąć więcej semantycznego znaczenia z kodu, ponieważ, jak często są rzeczy w rzeczywistym świecie? Twój kod może być bardziej czytelny, jeśli użyjesz wyjątków, aby wskazać błędy zamiast wartości null. I na koniec redukujesz potrzebę sprawdzania błędów, a także zmniejszasz ryzyko błędów czasu wykonywania (dreaded zerowy wskaźnik dereferencji).

Jeśli system nie został zaprojektowany z myślą null przypadkach, powiedziałbym, że najlepiej zostawić to w spokoju i nie zwariować zawijania wszystko z deref_or_throw. Być może podejmij podejście Agile. Podczas kodowania przejrzyj umowy, które twoje obiekty klasy oferują jako usługi. Jak często można rozsądnie oczekiwać, że umowy te zwrócą null? Jaka jest wartość semantyczna null w tych przypadkach?

Prawdopodobnie można zidentyfikować klasy w systemie, które mogą być rozsądnie oczekiwane, aby zwrócić null. Dla tych klas, null może być prawidłową logiką biznesową, a nie drobnymi sprawami wskazującymi na błędy implementacji. W przypadku klas, które mogą zostać zwrócone null, dodatkowa kontrola może być warta bezpieczeństwa. W tym sensie sprawdzenie, czy dane są bardziej logiczne, niż szczegóły dotyczące implementacji niskiego poziomu. Jednak na całej planszy systematyczne zawijanie całego wskaźnika oceny w deref_or_throw może być lekarstwem, które jest jego własną trucizną.

+0

Funkcja 'getObjectThatMayVeryRarelyBeNull()' powinna mieć możliwość zwrócenia 'null'. Jest to rzadkie, ale możliwe. Ale ponieważ jest to rzadkie, większość procedur (z wyjątkiem niektórych specjalnych) oczekuje, że zwróci obiekt. W przeciwnym razie są po prostu bez znaczenia. – Mikhail

+0

Byłbym skłonny usunąć wartość zerową jako prawidłową wartość zwracaną i być może rozważyć inną opcję wskazującą na bardzo rzadki przypadek. Nie znając twojego przypadku użycia, nie mogę spekulować dokładnie, co by to było. Wygląda na to, że twoja klasa może zwrócić "zaskakujący" wynik, że nie wszystkie osoby na utrzymaniu będą obsługiwać poprawnie, więc wydaje się niebezpieczne, aby zwrócić tam wartość zerową. I oczywiście, przyszli czytelnicy twojego kodu nie powinni odbierać subtelności, że 'getObjectThatMayVeryRarelyBeNull()' zwraca zero 0.01% czasu –

+0

To jest naprawdę trudne pytanie projektowe, i dlatego nie pytam o to :) Ograniczam obszar możliwych rozwiązań, aby nie można było zmienić interfejsu 'getObjectThatMayVeryRarelyBeNull()'. – Mikhail