Unikaj NULL

Opublikowane: 07/11/2015

w kategorii Zasoby.

Wstęp

Wartość null (NULL lub nullptr) w C++ może być użyta po to aby zasygnalizować że:

  • wartość nie jest jeszcze przypisana (i może nie będzie) lub
  • parametr nie został podany (wskaźnik z wartością domyślną = NULL)
  • wynik jest pusty (zwrócono null)

Jednakże taka sygnalizacja może być weryfikowana lub nie. Jeśli kod wywołujący nie sprawdzi wartości lub jej nie poda kiedy jest wymagana (poda null we wskaźniku), program ulegnie awarii w czasie run-time. Czasami jest to nieakceptowalna sytuacja, czasami sprawia to że analizowania innych problemów staje się bardziej skomplikowane. Dodatkowo, deklaracja parametrów funkcji jako wskaźników niekoniecznie oznacza, że możesz przesyłać null do tych parametrów.

Przed C++11 wartość null (NULL) była kompatybilna z typem integer, dlatego, kiedy miałeś dwa przeciążenia funkcji - dla int i wskaźnika, przesłanie NULL mogło wywołać pierwsze przeciążenie. Zobacz "Preferuj nullptr zamiast 0 i NULL" w "Skuteczny nowoczesny C++" napisane przez Scotta Meyersa (zobacz moją recenzję).

Aby ułatwić sobie życie, możesz spróbować ograniczyć używanie wartości nill w swoim programie używając następujących rozwiązań i technik.

Ogranicz używanie wskaźników

Zastąp wskaźniki referencjami kiedy parametry są wymagane. To zasygnalizuje że parametry nie mogą mieć wartości null, dzięki czemu użytkownikowi API będzie łatwiej używać Twoich funkcji.

A więc zamiast:

bool enroll_course_participant(Student *student) { }

Napisz:

bool enroll_course_participant(Student &student) { }

Możesz także próbować zmusić wywołujący kod do wykonania sprawdzenia NULL - poprzez użycie klas "optional" lub "not_null" - zobacz opis poniżej.

Użyj klasy "optional"

Klasa "Optional" może być użyta do wskazania że parametr może być podany lub nie.

Istnieją dwa rozwiązania które znam na dzień dzisiejszy: Boost.Optional oraz std::experimental::optional.

Przykład dla Boost:

class Student {
public:
  Student(const std::string &name): name_(name) {}
  virtual std::string getName() { return name_; }
protected:
  std::string name_;
}

Aby zwrócić opcjonalny wynik możesz użyć poniższego kodu jako przykładu:

boost::optional<Student> find_best_student() {
  if (list_.size() > 0) {
    return list_[0];
  } else {
    return boost::optional<Student>{};
  }      
}

cout << "Best student:\n";

boost::optional<Student> best = find_best_student();

if (best) { 
  show_student(best)
} else {
  cout << "Best student not found\n";
}

Użyj klasy "not_null"

Podobne do optional ale z odwrotnym znaczeniem: parametr musi być podany jako wartość nie-null (parametr jest wymagany i podawany jako wskaźnik).

Zaimplementowane w GSL i może być użyte jak poniżej:

void print_int(not_null<int *> intp) {
  cout << intp.get() << endl;
}

int a = 3;
int *aptr = &a;
print_int(aptr);

Wzorzec Obiektu Null

Zamiast zwracania wartości null możesz zwrócić obiekt, który implementuje wszystkie funkcje w sposób kompatybilny z API klasy, ale który nie zawiera żadnych wartości.

Przykład - część pierwsza (wspólna):

#include <iostream>
#include <vector>

using namespace std;

class StudentIntf {
public:
  virtual ~StudentIntf() {} // required for interface classes 
  virtual std::string getName() const = 0;  
};

class Student: public StudentIntf {
public:
  Student(const std::string &name): name_(name) {}
  virtual std::string getName() const { return name_; }
protected:
  std::string name_;
};

Przykład - część druga - z użyciem NULL:

class StudentGroup {
    std::vector<Student> students_;

 public:
    StudentIntf *find_best_student() {
      if (students_.size() > 0) {
        return &(students_[0]);
      } else {
        return NULL;
      }
    }
};

void show_student(const StudentIntf *student) {
   if (student != null) {
     cout << "Student: " << student.getName() << "\n";
   } else {
     cout << "Student: {empty}" << "\n";
   }
}

int main() {
    StudentGroup sg;
    cout << "Best student:\n";
    show_student(sg.find_best_student());

    return 0;
}

Przykład - część druga - z użyciem wzorca Obiektu Null (nie wymaga żadnej specjalnej obsługi po stronie klienta - zobacz show_student):

class StudentNullBest: public StudentIntf {
public:
   virtual std::string getName() const { return "{empty}"; }
};

class StudentGroup {
    std::vector<Student> students_;

 public:
    StudentIntf &find_best_student() {
      static StudentNullBest nullResult;

      if (students_.size() > 0) {
        return students_[0];
      } else {
        return nullResult;
      }
    }
};

void show_student(const StudentIntf &student) {
   cout << "Student: " << student.getName() << "\n";
};

int main() {
    StudentGroup sg;
    cout << "Best student:\n";
    show_student(sg.find_best_student());

    return 0;
}

Zobacz również

Udostępnij

obserwuj