smallB
smallB

Reputation: 17148

Problems with memory management strategy

in the newest The C++ Programming language book, Bjarne presents an alternative to an orthodox error handling, that is to use exceptions. This is in chapter 13.3.1, and he writes:

void test()
     // handle undiciplined resource acquisition
     // demonstrate that arbitrary actions are possible
{
     int* p = new int{7};                              // probably should use a unique_ptr (§5.2)
     int* buf = (int*)malloc(100*sizeof(int));         // C-style allocation

     auto act1 = finally([&]{   delete p;
                                free(buf);             // C-style deallocation
                                cout<< "Goodby, Cruel world!\n";
                           }
                     );

     int var = 0;
     cout << "var = " << var << '\n';

     // nested block:
     {
           var = 1;
           auto act2 = finally([&]{ cout<< "finally!\n"; var=7; });
           cout << "var = " << var << '\n';
     } // act2 is invoked here

     cout << "var = " << var << '\n';
}// act1 is invoked here

Although I understand what he is trying to explain and what this code is supposed to achieve, I have problem with believing that this fragment is leek free:

> 1.    int* p = new int{7};                              // probably should use a unique_ptr (§5.2)
>   2.           int* buf = (int*)malloc(100*sizeof(int));         // C-style allocation
>         
>     
> 3.         auto act1 = finally([&]{   delete p;
>                                         free(buf);             // C-style deallocation
>                                         cout<< "Goodby, Cruel world!\n";
>                                    }
>                              );

Why? Because if in line two (2) we get exception thrown (assuming that there isn't call to a malloc but to a function that can actually throw, as I believe the concept that Bjarne is trying to explain is to use the finally construct and the malloc call here is irrelevant and accidental and could be replaced by any call), then if two throws then 3 will never be reached and we have resource leek from 1. Am I correct?

Upvotes: 0

Views: 113

Answers (1)

111111
111111

Reputation: 16188

Basically what is happening in the above code segment is:

     int* p = new int{7};                              // probably should use a unique_ptr 
     int* buf = (int*)malloc(100*sizeof(int));         // C-style allocation

Memory is allocated both via C++ new and C malloc. Neither of the pointer are managed, and if any code at this point were to throw then memory would be leaked. However, note that malloc does not throw so in isolation this code is safe.

http://en.cppreference.com/w/cpp/memory/c/malloc

    auto act1 = finally([&]{   delete p;
                            free(buf);             // C-style deallocation
                            cout<< "Goodby, Cruel world!\n";
                       }
                 );

In this code an RAII type is returned from the finally function. Which will execute the callable parameter when act1 is destroyed (either on exception or on leaving this scope).

Because this directly follows the malloc we have now ensured that the memory can not leak.

The following code however would be unsafe:

      int* p = new int{7};                              // probably should use a unique_ptr 
     int* buf = (int*)malloc(100*sizeof(int));         // C-style allocation

     throwing_function();

     auto act1 = finally([&]{   delete p;
                            free(buf);             // C-style deallocation
                            cout<< "Goodby, Cruel world!\n";
                       }
                 );

However Bjarne comment is spot on, just use a std::unique_ptr.

EDIT:

A use of finally:

database_handle dh1;
dh1.do("action...");
finally([&]{
    if(!dh1.success()) {
        dh1.rollback();
    }
);

Upvotes: 1

Related Questions