Zwróć obiekt „NULL”, jeśli nie znaleziono wyniku wyszukiwania

95

Jestem całkiem nowy w C ++, więc podczas nauki staram się projektować z dużą ilością Java-izmów. W każdym razie w Javie, gdybym miał klasę z metodą „wyszukiwania”, która zwróciłaby obiekt Tz obiektu Collection< T >pasującego do określonego parametru, zwróciłbym ten obiekt i gdyby obiekt nie został znaleziony w kolekcji, wróciłbym null. Wtedy w mojej funkcji wywoływania po prostu sprawdzałbymif(tResult != null) { ... }

W C ++ dowiaduję się, że nie mogę zwrócić nullwartości, jeśli obiekt nie istnieje. Chcę tylko zwrócić „wskaźnik” typu T, który powiadamia funkcję wywołującą, że nie znaleziono żadnego obiektu. Nie chcę rzucać wyjątku, ponieważ tak naprawdę nie jest to wyjątkowa okoliczność.

Tak wygląda teraz mój kod:

class Node {
    Attr& getAttribute(const string& attribute_name) const {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            return NULL; // what should this be?
    }

private:
    vector<Attr> attributes;
}

Jak mogę to zmienić, żeby dać taki marker?

aduryk
źródło
6
Wyjątki i NULL nie zawsze są jedynymi rozwiązaniami. Często możesz wybrać wartość do zwrócenia wskazującą, że nie znaleziono: na przykład std::find(first, last, value)zwraca, lastjeśli żaden element nie pasuje.
Cascabel,

Odpowiedzi:

72

W C ++ odwołania nie mogą mieć wartości null. Jeśli chcesz opcjonalnie zwrócić wartość null, jeśli nic nie zostanie znalezione, musisz zwrócić wskaźnik, a nie referencję:

Attr *getAttribute(const string& attribute_name) const {
   //search collection
   //if found at i
        return &attributes[i];
   //if not found
        return nullptr;
}

W przeciwnym razie, jeśli nalegasz na zwrócenie przez odwołanie, powinieneś zgłosić wyjątek, jeśli atrybut nie zostanie znaleziony.

(Nawiasem mówiąc, trochę się martwię, że twoja metoda jest consti zwraca wartość niebędącą constatrybutem. Ze względów filozoficznych sugerowałbym powrót const Attr *. Jeśli chcesz również zmodyfikować ten atrybut, możesz przeciążać inną niż constmetodę zwracając również constatrybut niebędący atrybutem).

Jesse Beder
źródło
2
Dzięki. Nawiasem mówiąc, czy jest to akceptowany sposób projektowania takiej rutyny?
aduric
6
@aduric: Tak. Odniesienia sugerują, że wynik musi istnieć. Wskaźniki sugerują, że wynik może nie istnieć.
Bill
7
Ciekawe, czy wrócimy teraz nullptrzamiast NULLc ++ 11?
Spectral
1
tak, zawsze używaj nullptr zamiast NULL w C ++ 11 i nowszych. jeśli chcesz być wstecznie kompatybilny z wersjami wcześniejszymi, nie rób tego
Conrad Jones
56

Istnieje kilka możliwych odpowiedzi. Chcesz zwrócić coś, co może istnieć. Oto kilka opcji, od mojej najmniej preferowanej do najbardziej preferowanej:

  • Powrót przez odniesienie i wyjątek nie może znaleźć sygnału.

    Attr& getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            throw no_such_attribute_error;
    }

Jest prawdopodobne, że brak znalezienia atrybutów jest normalną częścią wykonania i dlatego nie jest wyjątkowy. Obsługa tego byłaby głośna. Nie można zwrócić wartości null, ponieważ jest to niezdefiniowane zachowanie, które zawiera odwołania o wartości null.

  • Powrót przez wskaźnik

    Attr* getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return &attributes[i];
       //if not found
            return nullptr;
    }

Łatwo zapomnieć o sprawdzeniu, czy wynik z getAttribute byłby wskaźnikiem innym niż NULL i jest łatwym źródłem błędów.

  • Użyj Boost.Optional

    boost::optional<Attr&> getAttribute(const string& attribute_name) const 
    {
       //search collection
       //if found at i
            return attributes[i];
       //if not found
            return boost::optional<Attr&>();
    }

A boost :: optional wskazuje dokładnie, co się tutaj dzieje i ma proste metody sprawdzania, czy taki atrybut został znaleziony.


Nota boczna: std :: optional był ostatnio głosowany w C ++ 17, więc będzie to "standardowa" rzecz w najbliższej przyszłości.

Kaz Dragon
źródło
+1 Chciałbym tylko wspomnieć o boost :: optional, a tylko pokrótce wspomnieć o innych alternatywach.
Nemanja Trifunovic
Tak, widziałem gdzieś podbicie :: opcjonalne, ale myślałem, że wymaga to zbyt dużego obciążenia. Jeżeli jest to najlepsze podejście do tego typu problemów, zacznę z niego korzystać.
aduric
boost::optionalnie wiąże się z dużym narzutem (brak alokacji dynamicznej), dlatego jest tak wspaniały. Używanie go z wartościami polimorficznymi wymaga zawijania odwołań lub wskaźników.
Matthieu M.
2
@MatthieuM. Jest prawdopodobne, że aduryk miał na myśli nie wydajność, ale koszt włączenia zewnętrznej biblioteki do projektu.
Swoogan
Dodatek do mojej odpowiedzi: zauważ, że szykuje się ruch do standaryzacji opcjonalnego jako komponentu standardowego, prawdopodobnie dla tego, co może być C ++ 17. Warto więc wiedzieć o tej technice.
Kaz Dragon,
22

Możesz łatwo utworzyć obiekt statyczny, który reprezentuje wartość NULL.

class Attr;
extern Attr AttrNull;

class Node { 
.... 

Attr& getAttribute(const string& attribute_name) const { 
   //search collection 
   //if found at i 
        return attributes[i]; 
   //if not found 
        return AttrNull; 
} 

bool IsNull(const Attr& test) const {
    return &test == &AttrNull;
}

 private: 
   vector<Attr> attributes; 
};

I gdzieś w pliku źródłowym:

static Attr AttrNull;
Mark Okup
źródło
Czy NodeNull nie powinien być typu Attr?
aduric
2

Jak już się zorientowałeś, nie możesz tego zrobić tak, jak zrobiłeś to w Javie (lub C #). Oto kolejna sugestia, którą możesz przekazać jako odwołanie do obiektu jako argument i zwrócić wartość bool. Jeśli wynik zostanie znaleziony w Twojej kolekcji, możesz przypisać go do przekazywanego odwołania i zwrócić „true”, w przeciwnym razie zwrócić „false”. Proszę wziąć pod uwagę ten kod.

typedef std::map<string, Operator> OPERATORS_MAP;

bool OperatorList::tryGetOperator(string token, Operator& op)
{
    bool val = false;

    OPERATORS_MAP::iterator it = m_operators.find(token);
    if (it != m_operators.end())
    {
        op = it->second;
        val = true;
    }
    return val;
}

Powyższa funkcja musi znaleźć operatora w kluczu „token”, jeśli znajdzie ten, który zwraca true i przypisać wartość do parametru Operator & op.

Kod dzwoniącego dla tej procedury wygląda następująco

Operator opr;
if (OperatorList::tryGetOperator(strOperator, opr))
{
    //Do something here if true is returned.
}
AB
źródło
1

Powodem, dla którego nie możesz zwrócić tutaj wartości NULL, jest to, że zadeklarowałeś swój typ zwrotu jako Attr&. Końcówka &sprawia, że ​​zwracana wartość jest „referencją”, która jest w zasadzie gwarantowanym wskaźnikiem, który nie jest zerowy, do istniejącego obiektu. Jeśli chcesz mieć możliwość zwrócenia wartości null, zmień Attr&na Attr*.

JSB ձոգչ
źródło
0

Nie możesz zwrócić, NULLponieważ typ zwracany funkcji jest obiektem, referencea nie plikiem pointer.

codaddict
źródło
-3

Możesz spróbować tego:

return &Type();
Utworzony
źródło
6
Chociaż ten fragment kodu może rozwiązać problem, dołączenie wyjaśnienia naprawdę pomaga poprawić jakość Twojego posta. Pamiętaj, że odpowiadasz na pytanie do czytelników w przyszłości, a osoby te mogą nie znać powodów, dla których zaproponowałeś kod.
NathanOliver
To prawdopodobnie zwróci martwe odniesienie do obiektu na stosie metod, prawda?
mpromonet