Avoid NULL

Overview

Null value (NULL or nullptr) can be used in C++ to indicate that:

  • value was not assigned yet (and maybe won't be assigned) or
  • parameter is not provided (pointer with default value = NULL)
  • result is empty (null returned)

However such an indication can be verified or not. If the calling code will fail to verify value or to provide value when it is required (passes null in pointer), program will fail in run-time. Sometimes it's not an acceptable situation, sometimes it just makes debugging other problems more complicated. Also, declaring function's parameters as pointers doesn't necessary mean you can pass null to these parameters.

Before C++11 null value (NULL) was compatible with integer, so when you had two function overloads - for int and pointer, passing NULL could invoke first overload. See "Prefer nullptr to 0 and NULL." in "Effective Modern C++" by Scott Meyers (see my review).

In order to ease your life, you can try to limit usage of null values in your program using the following solutions and techniques.

Limit usage of pointers

Replace pointers with references when parameters are required. This will indicate that parameters cannot be null so for API client it's easier to use your functions.

So instead of:

bool enroll_course_participant(Student *student) { }

Write:

bool enroll_course_participant(Student &student) { }

You can also try to force calling code to perform the NULL validation - by employing "optional" or "not_null" classes - see description below.

Use "optional" class

"Optional" class can be used to indicate that function parameter can be provided or not.

There are two solutions I'm aware of today: Boost.Optional and std::experimental::optional.

Simple example for Boost:

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

To return optional result you can use the following code as an example:

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";
}

Use "not_null" class

Similar to optional but with opposite meaning: parameter has to be provided as non-null value (parameter is required and passed as pointer).

It's implemented in GSL and can be used as below:

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

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

Null Object Pattern

Instead of returning null as a result you can return object which implements all functions in a way that it is compatible with class API, but does not contain any real-life values.

Example - first (common) part:

#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_;
};

Example - second part - with NULL usage:

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;
}

Example - second part - with Null Object pattern usage (doesn't need special handling for student-not-found result on client side - see 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;
}

See also

Share

follow