Sans Pareil Technologies, Inc.

Key To Your Business

Lab Exercise 4 - Pointers


We will build a simple project that will illustrate the benefits of using smart pointers over raw pointers. We will use a simple Resource class that helps us track whether a created instance was properly deallocated (destructor function called). We will throw exceptions to simulate a common problem that leads to memory leaks in systems that use dynamic memory allocation without care.

Create a new empty Visual Studio project named pointers and configure the project as in Lab 2.
Add the following files to the project and execute the test suite.
Resource.h 

Resource class declaration. Note that the test functions such as createAndThrow could easily just be implemented directly in the Resource.cpp file.

#pragma once
#include <string>

namespace csc240
{
  class Resource
  {
  public:
    Resource();
    ~Resource();
    Resource( const Resource& ) = delete;
    Resource& operator=( const Resource& ) = delete;
  };

  void createAndThrow();
  void createRawAndThrow();
  void createUniqueAndThrow();
  void createSharedAndThrow();

  bool status( const std::string& );
}
Resource.cpp 

Resource class and test function implementation.

#include "Resource.h"

#include <memory>
#include <sstream>
#include <unordered_map>

namespace csc240
{
  static std::unordered_map<std::string, bool> map;

  std::string addressOf( const Resource* r )
  {
    std::stringstream ss;
    ss << r;
    return ss.str();
  }

  Resource::Resource()
  {
    map[addressOf( this )] = true;
  }


  Resource::~Resource()
  {
    map[addressOf( this )] = false;
  }

  void createAndThrow()
  {
    Resource res;
    throw addressOf( &res );
  }

  void createRawAndThrow()
  {
    Resource* res{ new Resource() };
    throw addressOf( res );
  }

  void createUniqueAndThrow()
  {
    std::unique_ptr<csc240::Resource> uptr{ std::make_unique<csc240::Resource>() };
    throw addressOf( uptr.get() );
  }

  void createSharedAndThrow()
  {
    std::shared_ptr<csc240::Resource> uptr{ std::make_shared<csc240::Resource>() };
    throw addressOf( uptr.get() );
  }

  bool status( const std::string& a ) { return map.at( a ); }
}
pointers.cpp 

Test suite that illustrates the benefits of managed pointers over raw pointers.

#define CATCH_CONFIG_MAIN
#include "catch.hpp"
#include "Resource.h"

SCENARIO( "Stack instances" )
{
  GIVEN( "A Resource created on the stack" )
  {
    WHEN( "Resource created on stack and exception is thrown" )
    {
      std::string address;

      try
      {
        csc240::createAndThrow();
      }
      catch ( const std::string& ex )
      {
        address = ex;
      }

      THEN( "Status of Resource is false as destructor was called" )
      {
        REQUIRE_FALSE( csc240::status( address ) );
      }
    }
  }
}

SCENARIO( "Unsafe dynamic memory allocation" )
{
  GIVEN( "A Resource created on the heap" )
  {
    WHEN( "Resource created with new and an exception is thrown" )
    {
      std::string address;

      try
      {
        csc240::createRawAndThrow();
      }
      catch ( const std::string& ex )
      {
        address = ex;
      }

      THEN( "Status of Resource is true as destructor was not called" )
      {
        REQUIRE( csc240::status( address ) );
      }
    }
  }
}

SCENARIO( "Safe dynamic memory allocation with smart pointers (RAII)" )
{
  GIVEN( "A std::unique_ptr of Resource" )
  {
    WHEN( "Resource created and an exception is thrown" )
    {
      std::string address;

      try
      {
        csc240::createUniqueAndThrow();
      }
      catch ( const std::string& ex )
      {
        address = ex;
      }

      THEN( "Status of Resource is false as destructor was called" )
      {
        REQUIRE_FALSE( csc240::status( address ) );
      }
    }
  }

  GIVEN( "A std::shared_ptr of Resource" )
  {
    WHEN( "Resource created and an exception is thrown" )
    {
      std::string address;

      try
      {
        csc240::createSharedAndThrow();
      }
      catch ( const std::string& ex )
      {
        address = ex;
      }

      THEN( "Status of Resource is false as destructor was called" )
      {
        REQUIRE_FALSE( csc240::status( address ) );
      }
    }
  }
}
The csc240::Resource class constructor adds the memory address of the allocated instance to a std::unordered_map with a value of true. In the destructor, we will update the map with a value of false. We then add a few utility functions that will create instances of the Resource class on the stack and heap and then throw an exception with the memory address of the instance that was created. We will then ensure that the status has been correctly set to false, proving that the destructor was called as expected when using RAII with dynamic memory allocation. Note that the result will still show true when we use new to allocate dynamic memory without using RAII, indicating that in that scenario our code would have leaked memory.