// allocate2.cpp
// Glenn G. Chappell
// 23 Sep 2009
//
// For CS 311 Fall 2009
// Demonstration of out-of-memory handling using exceptions

#include <iostream>
using std::cout;
using std::endl;
using std::cin;
#include <new>
using std::bad_alloc;


// allocate1
// Given the size of an array to allocate, attempts to
// allocate a dynamic array of int's of the given size,
// and returns a pointer to this in iptr. Old value of iptr
// is discarded. Throws std::bad_alloc if allocation is
// unseuccessful.
// Pre:
//     size >= 0.
// Post:
//     iptr points to array of size int's, allocated with
//      new [], ownership transfered to caller.
// May throw std::bad_alloc.
void allocate1(size_t size,
               int * & iptr)
{
    // UNCOMMENT NEXT LINE TO SEE WHAT HAPPENS IF THE FOLLOWING "new" THROWS
    //throw std::bad_alloc();
    // NOTE: IN PRACTICE, WE **NEVER** DO SUCH A THROW; "new" DOES THE THROW

    iptr = new int[size];
    // If the allocation fails, the "new" throws, and we
    // leave. No clean-up necessary.
}


// allocate2
// Given the size of two arrays to allocate, attempts to
// allocate two dynamic arrays of int's, both of the given
// size, and returns pointers to these in iptr1, iptr2,
// respectively. Old values of iptr1, iptr2 are discarded.
// Throws std::bad_alloc if either allocation is
// unsuccessful.
// Pre:
//     size >= 0.
// Post:
//     iptr1 points to array of size int's, allocated with
//      new [], ownership transfered to caller.
//     iptr2 points to array of size int's, allocated with
//      new [], ownership transfered to caller.
// May throw std::bad_alloc.
void allocate2(size_t size,
               int * & iptr1,
               int * & iptr2)
{
    // UNCOMMENT NEXT LINE TO SEE WHAT HAPPENS IF THE FOLLOWING "new" THROWS
    //throw std::bad_alloc("Out of memory");
    // NOTE: IN PRACTICE, WE **NEVER** DO SUCH A THROW; "new" DOES THE THROW

    iptr1 = new int[size];
    // If the 1st allocation fails, the "new" throws, and we
    // leave. No clean-up necessary.

    try
    {
        // UNCOMMENT NEXT LINE TO SEE WHAT HAPPENS IF THE FOLLOWING "new" THROWS
        //throw std::bad_alloc("Out of memory");
        // NOTE: IN PRACTICE, WE **NEVER** DO SUCH A THROW; "new" DOES THE THROW

        iptr2 = new int[size];
        // If the 2nd allocation fails, we need to clean up
        // the first.
    }
    catch (...)
    {
        delete [] iptr1;
        throw;  // Re-throw same exception
    }
}


// Main program
// Demonstrates calling allocate1, allocate2,
// with proper catching.
int main()
{
    // Call allocate1
    bool allocate1Successful;  // true if allocation is successful
    int * ip1;
    try
    {
        allocate1(20, ip1);
        allocate1Successful = true;
        cout << "Call to \"allocate1\" successful" << endl;
    }
    catch (std::bad_alloc & e)
    {
        allocate1Successful = false;
        cout << "Call to \"allocate1\" NOT successful - message: " << e.what() << endl;
    }

    // Call allocate2
    bool allocate2Successful;  // true if allocations are successful
    int * ip2a;
    int * ip2b;
    try
    {
        allocate2(10, ip2a, ip2b);
        allocate2Successful = true;
        cout << "Call to \"allocate2\" successful" << endl;
    }
    catch (std::bad_alloc & e)
    {
        allocate2Successful = false;
        cout << "Call to \"allocate2\" NOT successful - message: " << e.what() << endl;
    }

    // Now we deallocate the arrays that were successfully allocated
    if (allocate1Successful)
    {
        cout << "Deallocating arrays from \"allocate1\"" << endl;
        delete [] ip1;
    }
    if (allocate2Successful)
    {
        cout << "Deallocating arrays from \"allocate2\"" << endl;
        delete [] ip2a;
        delete [] ip2b;
    }

    cout << "Press ENTER to quit ";
    while (cin.get() != '\n') ;

    return 0;
}

