Really Old Geek

The New York Times published an article about Donald Knuth “The Yoda of Silicon Valley”. The article might idolize his work and impact, but provides a great overview of his work. Mr. Knuth is a living legend in the field of computer science, known mainly for his seminal book on algorithms “The Art Of Computer Programming (TAOCP)”.

Given Knuth’s life-time work on algorithms, the following quote from him might be surprising: “I am worried that algorithms are getting too prominent in the world. It started out that computer scientists were worried nobody was listening to us. Now I’m worried that too many people are listening.” I think it is in direct reference to usage of algorithms and their biases in Machine Learning (ML).

For a different take on TAOCP, read this post on Medium, where the author jokes that a lot of people claim how important the book is, without actually reading it.

Why is smart pointer better than a raw one?

In a previous post I suggested that raw pointers should be replaced by smart pointers. This post explains why smart pointers are better than raw ones and how to convert an existing code using raw pointers to use smart pointers. For the sake of this post, when I refer to smart pointers, I talk about unique pointers, unique_ptr.

Why is smart pointer better?

Smart pointers have one crucial advantage over raw pointers: they guarantee that their destructors are called during stack unwinding. That means that any memory allocated in constructors will be automatically freed.

Let’s look at sample code, that illustrates the advantage of smart pointers over raw pointers. First, let’s define a simple class that owns some memory, say std::string to store text.

class Test
{
 string s;
public:
 Test(string sVariable) :
  s(sVariable)
 {
  cout << "Constructor (" << s << ")..." << endl;
 };
 ~Test()
 {
  cout << "Destructor (" << s << ")..." << endl;
 };
};

Now assume that the main function will call myFunction() method to perform some operations. Main will catch any exceptions generated in myFunction() and exit the program.

int main()
{
 try
 {
  myFunction();
 }
 catch(...)
 {
  cout << "Exception handled" << endl;
 }
 
 cout << "End" << endl;
 return 0;
}

Now let’s declare two pointers to Test class in myFunction(). First is rawPointer, which is raw pointer, and then smartPointer which is a smart pointer. Then let’s throw an exception to simulate some unexpected behavior of myFunction. At the end of the function we delete rawPointer. Note, that a smartPointer variable is not deleted as its destructor is guaranteed when the variable goes out of scope.

int myFunction()
{
 cout << "raw pointer" << endl;
 Test *rawPointer = new Test("raw pointer");
 
 cout << "smart pointer" << endl;
 unique_ptr<Test> smartPointer = make_unique<Test>("smart pointer");
 
 cout << "Throw!" << endl;
 throw;
 
 cout << "Delete raw pointer" << endl;
 delete rawPointer;
 
 return 0;
}

When we run this code, we see the following output:

raw pointer
Constructor (raw pointer)...
smart pointer
Constructor (smart pointer)...
Throw!
Destructor (smart pointer)...
Exception handled
End

Notice, that rawPointer variable is never deleted, causing a memory leak to occur.

How to convert raw pointers to smart pointers?

The above code provides a template how to convert existing code that is using raw pointers to smart pointers. To simplify the instructions, let’s use an example of code using raw pointers to int.

  • Convert raw pointer declaration and allocation from
    int * rP;
    rP = new int;

    to
    unique_ptr<int> sP = make_unique<int>();
  • Remove delete call to release rP memory

That’s it! The code after the conversion is simpler and more robust, as you will never forget to delete the memory, or delete it twice, or the delete will simply be skipped by unexpected code flow (see the example using throw).

When is C pointer valid?

C is largely based on raw pointers and one of the most often asked questions is when is a pointer a valid one. The reason is that dereferencing an invalid pointer generates an access violation exception (0xC0000005) and if not caught, the program crashes.

You quickly learn there is IsBadReadPtr function available and its variants. You look at the name and think you found the best way to check the pointer’s validity. That is until you notice, that Microsoft documentation says these functions are obsolete and warns against using them in no uncertain terms. And Raymond Chan from Microsoft provides details at his blog The Old New Thing.

Then, you think this is such basic C/C++ question, there must be a way to do it. You start searching the Internet and slowly come to realize, that there is no way to check validity of raw pointers.

However, there are best practices how to guard against invalid pointers.

Best practices to guard against bad pointers

  • Initialize all pointers to nullptr.
  • After deleting an object, set its pointer to nullptr.
  • Check pointer for non-zero value before dereferencing it.

The above best practices will not prevent you from dereferencing invalid raw pointers altogether. For example, it does not prevent somebody from passing an invalid pointer through the API. In that situation, you have to accept GIGO, garbage in garbage out paradigm.

How to check if pointer has non-zero value?

There are few ways of checking if a pointer points to nothing. If it does not, it has a non-zero value. Still does not make it valid, but you have done your best.

  • if (p == 0): Check against number 0 which is typed as int. OK approach.
  • if (p == NULL): NULL is macro defined as 0. Slightly better, makes your application more readable.
  • if (p == nullptr): Check against C++11 keyword. Best, clean approach.

The above triad is nicely explained by Kate Gregory in her “C++ Fundamentals” class on Pluralsight, in chapter “Pointers”. For another take on the same read Bjarne Stroustrup.

Conclusion

There is no good way to check if a raw pointer is valid or not, in other words if it can be safely dereferenced or not. You should seriously consider not using raw pointers in your code and start using smart pointers unique_ptr and shared_ptr from C++11.