Rule A15-1-4 (required, implementation, partially automated)
If a function exits with an exception, then before a throw, the function shall place all objects/resources that the function constructed in valid states or it shall delete them.
Rationale
If the only handler to dynamically allocated memory or system resource (e.g. file, lock, network connection or thread) goes out of scope due to throwing an exception, memory leak occurs. Memory leaks lead to performance degradation, security violations and software crashes. Allocated memory or system resource can be released by explicit call to resource deinitialization or memory deallocation function (such as operator delete), before each return/try/break/continue statement. However, this solution is error prone and difficult to maintain. The recommended way of releasing dynamically allocated objects and resources is to follow RAII ("‘Resource Acquisition Is Initialization"’) design pattern, also known as Scope-Bound Resource Management or “Constructor Acquires, Destructor Releases” (CADRe). It allows to bind the life cycle of the resource to the lifetime of a scope-bound object. It guarantees that resources are properly deinitialized and released when data flow reaches the end of the scope.
Examples of RAII design pattern that significantly simplifies releasing objects/resources on throwing an exception are C++ smart pointers: std::unique_ptr and std::shared_ptr.
Example
//% $Id: A15-1-4.cpp 289436 2017-10-04 10:45:23Z michal.szczepankiewicz $
#include <cstdint>
#include <memory>
#include <stdexcept>
extern std::uint32_t F1();
void FVeryBad() noexcept(false)
{
std::logic_error* e = new std::logic_error("Logic Error 1");
// ...
std::uint32_t i = F1();
if (i < 10)
{
throw(*e); // Non-compliant - fVeryBad() is not able to clean-up
// allocated memory
}
// ...
delete e;
}
void FBad() noexcept(false)
{
std::int32_t* x = new std::int32_t(0);
// ...
std::uint32_t i = F1();
if (i < 10)
{
throw std::logic_error("Logic Error 2"); // Non-compliant - exits from
// fBad()
without cleaning-up
// allocated resources and
// causes a memory leak
}
else if (i < 20)
{
throw std::runtime_error("Runtime Error 3"); // Non-compliant - exits
// from fBad()
without
// cleaning-up
allocated
// resources and causes a
// memory leak
}
// ...
delete x; // Deallocates claimed resource only in the end of fBad() scope
}
void FGood() noexcept(false)
{
std::int32_t* y = new std::int32_t(0);
// ...
std::uint32_t i = F1();
if (i < 10)
{
delete y; // Deletes allocated resource before throwing an exception
throw std::logic_error("Logic Error 4"); // Compliant - deleting y
// variable before exception
// leaves the fGood() scope
}
else if (i < 20)
{
delete y; // Deletes allocated resource before throwing an exception
throw std::runtime_error("Runtime Error 5"); // Compliant - deleting y
// variable before
// exception leaves the
// fGood() scope
}
else if (i < 30)
{
delete y; // Deletes allocated resource before throwing an exception
// again, difficult to maintain
throw std::invalid_argument(
"Invalid Argument 1"); // Compliant
- deleting
// y variable before
// exception
leaves the
// fGood() scope
}
// ...
delete y; // Deallocates claimed resource also in the end of fGood() scope
}
void FBest() noexcept(false)
{
std::unique_ptr<std::int32_t> z = std::make_unique<std::int32_t>(0);
// ...
std::uint32_t i = F1();
if (i < 10)
{
throw std::logic_error("Logic Error 6"); // Compliant - leaving the
// fBest() scope causes
// deallocation of all
// automatic variables, unique_ptrs, too
}
else if (i < 20)
{
throw std::runtime_error("Runtime Error 3"); // Compliant - leaving the
// fBest() scope causes
// deallocation
of all
// automatic variables,
// unique_ptrs,
too
}
else if (i < 30)
{
throw std::invalid_argument(
"Invalid Argument 2"); // Compliant - leaving the fBest() scope
// causes deallocation of all automatic
// variables, unique_ptrs,
// too
}
// ...
// z is deallocated automatically here, too
}
class CRaii // Simple class that follows RAII pattern
{
public:
CRaii(std::int32_t* pointer) noexcept : x(pointer) {}
~CRaii()
{
delete x;
x = nullptr;
}
private:
std::int32_t* x;
};
void FBest2() noexcept(false)
{
CRaii a1(new std::int32_t(10));
// ...
std::uint32_t i = F1();
if (i < 10)
{
throw std::logic_error("Logic Error 7"); // Compliant - leaving the
// fBest2()
scope causes a1
// variable
deallocation
// automatically
}
else if (i < 20)
{
throw std::runtime_error("Runtime Error 4"); // Compliant - leaving the
// fBest2() scope causes
Guidelines for the use of the C++14 language in
critical and safety-related systems
// a1 variable
// deallocation
// automatically
}
else if (i < 30)
{
throw std::invalid_argument(
"Invalid Argument 3"); // Compliant - leaving the fBest2() scope
// causes a1 variable deallocation
// automatically
}
// ...
// a1 is deallocated automatically here, too
}
See also
SEI CERT C++ [10]: ERR57-CPP. Do not leak resources when handling exceptions C++ Core Guidelines [11]: E.6: Use RAII to prevent leaks.