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