Boost C++ Libraries

...one of the most highly regarded and expertly designed C++ library projects in the world. Herb Sutter and Andrei Alexandrescu, C++ Coding Standards

PrevUpHomeNext

Scope guards

Conditional scope guards: scope_exit, scope_success and scope_fail
Scope guard condition functions
Unconditional scope guard: defer_guard
Caveats of capturing by reference
Setting up scope exit actions at run time
Comparison with Boost.ScopeExit library
Comparison with scope guards defined in C++ Extensions for Library Fundamentals

A scope guard is an object that invokes an arbitrary action function object on destruction. Scope guards are useful for implementing actions that need to be reliably performed upon control leaving an execution scope (for example, when returning from a function), which is especially helpful for handling exceptions.

The wrapped action function object is specified on the scope guard construction and cannot be changed afterwards. Its type must be one of:

Note that if the wrapped function is a reference to a function object, that object must be stored externally to the scope guard and must remain valid for the entire lifetime of the scope guard.

Some scope guards also support specifying an additional condition function object, which allows for customizing the conditions in which the action function object must be called. Condition function objects are discussed in more detail in a later section.

Boost.Scope provides four kinds of scope guards, differing in their features and conditions upon which the action function object is called, summarised in the table below.

Table 1.2. Scope guard comparison

Feature

scope_exit

scope_success

scope_fail

defer_guard

Supports a condition function?

Yes

Yes

Yes

No

Invokes action on normal scope exit?

Yes, by default. Depends on condition.

Yes

No

Yes

Invokes action on scope exit due to failure?

Yes, by default. Depends on condition.

No

Yes

Yes

Action may throw?

Yes, by default. Depends on condition.

Typically yes

Typically no

Yes

Can be (de)activated?

Yes

Yes

Yes

No

Move-constructible? (requires function objects to be move-constructible)

Yes

Yes

Yes

No

Has factory function? (C++11-friendly)

Yes

Yes

Yes

No


In the table above, the term "failure" is used broadly. What constitutes a failure in a given context is specified by user in the form of the condition function object. Most often, a thrown exception is taken as an indication of a failure, but it can be changed to, for example, a check for an error code that is being returned from the enclosing function.

For scope_exit, there is no notion of "success" or "failure". Rather, the scope guard invokes the action function object if the condition returns true. By default, if the condition function object is not specified by user, the scope guard operates as if the condition always returns true, which means, for example, that it will invoke the action whether the control leaves the scope normally or due to an exception.

Although it is possible to specify arbitrary condition function objects, the intended use case for scope_success is to invoke its action when the scope is left normally (i.e. not because of a failure) and for scope_fail - to invoke its action to handle errors, including exceptions. For this reason, action functions that are used with scope_fail are typically not allowed to throw, as this may cause the program to terminate. This is also a concern with other scope guards, such as scope_exit and defer_guard, which also may invoke their actions due to an exception. It is user's responsibility to ensure that scope guard actions don't throw if another exception is being propagated. Generally, it is recommended to use scope guards to implement actions that cannot throw and move all operations that may fail to the normal code flow.

#include <boost/scope/scope_exit.hpp>
#include <boost/scope/scope_success.hpp>
#include <boost/scope/scope_fail.hpp>

The scope_exit, scope_success, and scope_fail scope guards have a lot of similarities in interfaces and capabilities and differ in conditions when they invoke the action function object. As shown in the table above, these scope guards support specifying an additional condition function object. By default, scope_success will invoke its action if it is destroyed normally, scope_fail - if it is destroyed due to an exception being thrown. scope_exit by default operates as if the condition always allows executing the action.

[Tip] Tip

Condition function objects will be discussed in more detail in the next section. Note that there are caveats with detecting whether an exception has been thrown, also discussed in the next section.

Given the different default condition function objects, the different scope guard types are typically useful for different purposes:

  • scope_exit is useful for general cleanup tasks, which need to be performed regardless of success or failure of the enclosing function.
  • scope_success can be used for common finalization code that needs to be performed on successful completion of the function.
  • scope_fail is intended for handling errors, for example, to revert a partially complete operation to the previous valid state.

All scope guards are especially useful if there are multiple return points (both normal and via exception), and when the scope guard action is not easy to extract as a function.

Let's consider an example of a class that receives network requests, validates them and passes to session objects to which each request corresponds. After receiving a request, the class must send a response - either a successful one, with the results of processing the request, or an error.

// A network request receiver
class receiver
{
    // A network connection
    connection m_conn;
    // A collection of sessions, searchable by session id
    std::map< std::string, session > m_sessions;

public:
    // Processes a network request and sends a response
    void process_request(request const& req)
    {
        const auto start_time = std::chrono::steady_clock::now();
        boost::scope::scope_exit time_guard([start_time]
        {
            // Log the amount of time it took to process the request
            const auto finish_time = std::chrono::steady_clock::now();

            std::clog << "Request processed in "
                << std::chrono::duration_cast< std::chrono::milliseconds >(finish_time - start_time).count()
                << " ms" << std::endl;
        });

        response resp;

        boost::scope::scope_fail failure_guard([&]
        {
            // Disconnect in case of exceptions
            m_conn.disconnect(); // doesn't throw
        });

        boost::scope::scope_success success_guard([&]
        {
            // Send a response that was populated while processing the request
            m_conn.send_message(resp);
        });

        // Validate the request and populate the response based on the contents of the request
        if (req.start_line().version > max_supported_version)
        {
            resp.start_line().version = max_supported_version;
            resp.start_line().set_status(protocol_version_not_supported);
            return;
        }

        resp.start_line().version = req.start_line().version;

        auto it_cseq = req.headers().find(cseq_header);
        if (it_cseq == req.headers().end())
        {
            resp.start_line().set_status(bad_request);
            resp.set_body(mime::text_plain, "CSeq not specified");
            return;
        }

        resp.headers().add(cseq_header, *it_cseq);

        auto it_session_id = req.headers().find(session_id_header);
        if (it_session_id == req.headers().end())
        {
            resp.start_line().set_status(bad_request);
            resp.set_body(mime::text_plain, "Session id not specified");
            return;
        }

        resp.headers().add(session_id_header, *it_session_id);

        // Find the session to forward the request to
        auto it_session = m_sessions.find(*it_session_id);
        if (it_session == m_sessions.end())
        {
            resp.start_line().set_status(session_not_found);
            return;
        }

        // Process the request in the session and complete populating the response
        it_session->second.process_request(req, resp);
    }
};

In the example above, the scope guards automatically perform their respective actions regardless of the return point of the process_request method. success_guard makes sure the response is sent, whether it is a successful response after handling the request in the target session object, or one of the error responses. failure_guard handles the case when the processing fails with an exception and terminates the connection in this case. Note that as failure_guard is destroyed after success_guard, it also covers errors that might occur while sending the response. Finally, time_guard logs the time it took to process the request, whether successfully or with an exception, for diagnostic purposes.

Each of the scope guards described in this section supports active and inactive states. The action function object will only be called if the scope guard is in active state while being destroyed. By default, scope guards are created in the active state, but this can be changed by passing false as the last argument for the constructor. Scope guards can also be deactivated or activated during their lifetime by calling the set_active method. Deactivating the scope guard is often used as a way to "commit" the effects of the block of code between the scope guard creation and deactivation. Manual activation of a scope guard can sometimes be useful if the scope guard needs to be created before it becomes known whether the scope guard action needs to be executed (e.g. when the scope guard needs to be created at an outer scope compared to where the decision on whether it should be enabled is made).

class collection
{
    std::map< int, object > objects;

public:
    // Finds an existing object by key or creates a new one and returns a reference to it
    object& obtain_object(int key)
    {
        // Find the object or the position where it should be
        auto it = objects.lower_bound(key);

        // Create an inactive scope guard initially. Needs to be created
        // at this scope to cover the on_requested() call below.
        boost::scope::scope_fail rollback_guard{[&]
        {
            // Remove the object that was just inserted
            objects.erase(it);
        },
        false};

        if (it == objects.end() || it->first != key)
        {
            // Insert a new object that correspond to the specified key
            it = objects.emplace_hint(it, key, object{});

            // Activate rollback guard to revert the insertion in case of error
            rollback_guard.set_active(true);

            // Initialize the inserted object. May throw.
            it->second.initialize(*this, key);
        }

        // Notify the object that is has been requested. May throw.
        it->second.on_requested();

        return it->second;
    }
};

The code samples above rely on C++17 class template argument deduction (CTAD) for the scope guard types to deduce the function object type (which is the lambda). If this feature is not available, the scope guard construction can be rewritten using a factory function, like this:

auto rollback_guard = boost::scope::make_scope_fail([&]
{
    objects.erase(it);
},
false);

Factory functions are provided for each of the three scope guards described in this section and are compatible with C++11. The factory functions are named as make_<scope_guard> and accept the same arguments as the corresponding scope guard's constructor.

Scope guards described in this section are move-constructible (but not assignable), which requires the wrapped function objects to be move- or copy-constructible as well. After moving, the moved-from scope guard becomes inactive. If a moved-from scope guard is active on destruction, the behavior is undefined.

#include <boost/scope/exception_checker.hpp>
#include <boost/scope/error_code_checker.hpp>

As discussed before, scope_exit, scope_success and scope_fail support specifying an additional condition function object that will be called to decide whether the scope guard should invoke the action. A condition function object must satisfy the following requirements:

  • the type of a condition function object must be one of:
    • a user-defined class with a public operator() taking no arguments, or
    • an lvalue reference to such class, or
    • an lvalue reference or pointer to a function taking no arguments;
  • invoking the condition function object must return a value contextually convertible to bool, and
  • invoking the condition function object must not throw exceptions.

For all the three scope guards, the condition function object is optional. If not specified, scope_exit operates as if the condition function object always returns true and will invoke its action function object as long as the scope guard is active. Otherwise, the scope guard only invokes its action if the condition returns true.

For scope_success and scope_fail, the exception_checker condition function is used by default. It works by capturing the number of uncaught exceptions on construction (which happens at the point of construction of the scope guard) and then comparing the captured value with the number of uncaught exceptions when it is called (which happens when the scope guard is destroyed). If the number has increased, it is taken as a sign that a new exception is in flight, and the predicate returns true; otherwise, the predicate returns false.

[Note] Note

By design, exception_checker is intended for a specific use case with scope guards created on the stack. It is incompatible with C++20 coroutines and similar facilities (e.g. fibers and userspace context switching), where the thread of execution may be suspended after the predicate captures the number of uncaught exceptions and then resumed in a different context, where the number of uncaught exceptions has changed. Similarly, it is incompatible with usage patterns where the predicate is cached after construction and is invoked after the thread has left the scope where the predicate was constructed (e.g. when the predicate or the associated scope guard is stored as a class data member or a namespace-scope variable).

scope_success invokes its action when the condition returns false (i.e. when the failure is not detected) and scope_fail - when the condition returns true (i.e. when the failure is detected). In relation to these two scope guard types, the condition function object is sometimes called the failure condition because it indicates whether a failure has happened.

You may notice that scope_exit behavior (with a user-specified condition function object) is similar to scope_fail and opposite to scope_success in terms of their usage of condition function objects. The main difference is a semantic one: scope_exit does not have a success/failure connotation and may be used with arbitrary condition functions. On the other hand, scope_success and scope_fail typically correspond to their respective intended use cases, and the default condition function makes them equivalent to the scope guards defined in <experimental/scope>.

It is possible to emulate each of the scope guards described in this section by using scope_exit, either with a custom condition function object, with the condition function embedded into the action function, or by activating or deactivating the scope guard after construction. For example, the following four pieces of code have the same effect:

Table 1.3. Comparison of scope_fail and scope_exit

scope_fail

scope_exit with a condition

scope_exit with embedded condition

scope_exit with manual deactivation

void push_back(int x, std::vector< int >& vec1,
    std::vector< int >& vec2)
{
    vec1.push_back(x);

    // Revert vec1 modification on failure
    boost::scope::scope_fail rollback_guard{[&]
    {
        vec1.pop_back();
    }};

    vec2.push_back(x);
}
void push_back(int x, std::vector< int >& vec1,
    std::vector< int >& vec2)
{
    vec1.push_back(x);

    // Revert vec1 modification on failure
    boost::scope::scope_exit rollback_guard
    {
        [&] { vec1.pop_back(); },
        boost::scope::exception_checker()
    };

    vec2.push_back(x);
}
void push_back(int x, std::vector< int >& vec1,
    std::vector< int >& vec2)
{
    vec1.push_back(x);

    // Revert vec1 modification on failure
    boost::scope::scope_exit rollback_guard
    {
        [&, uncaught_count = std::uncaught_exceptions()]
        {
            if (std::uncaught_exceptions() > uncaught_count)
                vec1.pop_back();
        }
    };

    vec2.push_back(x);
}
void push_back(int x, std::vector< int >& vec1,
    std::vector< int >& vec2)
{
    vec1.push_back(x);

    // Revert vec1 modification on failure
    boost::scope::scope_exit rollback_guard{[&]
    {
        vec1.pop_back();
    }};

    vec2.push_back(x);

    // Commit vec1 modification
    rollback_guard.set_active(false);
}

The main benefit of using scope_fail in this example is reducing code size and complexity. You can see that adding the check for the number of uncaught exceptions to the scope guard action makes it notably more verbose, and if such a check needs to be performed in multiple scope guards, this code would have to be duplicated. Explicitly deactivating the scope guard also has its downsides, as it may be prone to errors if it has to be performed in multiple places of the code (for example, when there are multiple return points, where the transaction needs to be committed). This goes against the intended purpose of the scope guard, as it was supposed to automate the correct execution of its action without the user having to manually ensure this at every possible point of leaving the scope.

Another benefit of scope_fail is improved code readability. For a reader, scope_fail immediately indicates an error handler, whereas scope_exit does not have such connotation and may contain a general cleanup code.

That being said, there may be reasons to still use one of the techniques demonstrated above instead of scope_success/scope_fail, especially when paired with exception_checker. As noted above, exception_checker is incompatible with coroutines and similar facilities, so using a manually managed scope_exit can be a solution. Another consideration is performance, specifically in relation with exception_checker. This predicate has to invoke runtime functions to obtain the number of uncaught exceptions, and has to do this twice - on scope guard construction and destruction. Although these functions are usually cheap, these calls are typically not optimized away by the compiler, even if no exception is thrown, and deactivating a scope guard is cheaper still. So, if a scope guard is used in a tight loop where its performance overhead may be significant, preferring scope_exit with manual deactivation may be a reasonable choice.

scope_exit, scope_success and scope_fail accept the condition function object as the second argument for the constructor or factory function, after the action function object. If the condition function object is default-constructible and the default constructor doesn't throw, the function object may be omitted from the scope guard constructor arguments.

// Writes a string to a file using a file descriptor, with file locking.
// Reports errors via an error_code parameter.
void locked_write_string(int fd, std::string const& str, std::error_code& ec)
{
    int err = 0;

    // Automatically transform errors to error_code on exit
    boost::scope::scope_fail fail_guard
    {
        // Action function object
        [&] { ec = std::error_code(err, std::generic_category()); },
        // Condition function object
        [&err]() noexcept { return err != 0; }
    };

    // Performs a lock operation on the file descriptor, returns error code or 0 on success
    auto lock_operation = [fd](int operation)
    {
        while (flock(fd, operation) < 0)
        {
            int err = errno;
            if (err != EINTR)
                return err;
        }

        return 0;
    };

    // Lock the file
    err = lock_operation(LOCK_EX);
    if (err != 0)
        return;

    // Unlock the file on exit
    BOOST_SCOPE_DEFER [&] { lock_operation(LOCK_UN); };

    // Write data
    const char* p = str.data();
    std::size_t size_left = str.size();
    while (size_left > 0)
    {
        ssize_t written = write(fd, p, size_left);
        if (written < 0)
        {
            err = errno;
            if (err == EINTR)
            {
                err = 0; // we do not want to report EINTR to the caller
                continue;
            }
            return;
        }

        p += written;
        size_left -= written;
    }
}

Besides exception_checker, the library also provides error_code_checker that can be used as a condition function object. On construction, error_code_checker captures a reference to an external error code object and checks it for an error indication when being called. This implies that the error code object must remain valid for the entire lifetime duration of the error_code_checker predicate.

An object ec can be used as an error code object if:

  • the expression !ec is valid, never throws, and returns a value contextually convertible to bool, and
  • the expression !ec produces a value contextually convertible to true when there is no error and false otherwise.

That is, for an error code object ec, invoking error_code_checker results in a value equivalent to !!ec. This makes error_code_checker compatible with a wide variety of error code types, including:

  • std::error_code or boost::system::error_code from Boost.System,
  • std::expected, boost::outcome_v2::basic_outcome or boost::outcome_v2::basic_result from Boost.Outcome,
  • int, where the value of 0 indicates no error,
  • bool, where the value of false indicates no error,
  • T*, where a null pointer indicates no error.

For C++11 compilers, the library also provides a factory function check_error_code. For example, our previous example could use this condition function object like this:

// Writes a string to a file using a file descriptor, with file locking.
// Reports errors via an error_code parameter.
void locked_write_string(int fd, std::string const& str, std::error_code& ec)
{
    int err = 0;

    // Automatically transform errors to error_code on exit
    boost::scope::scope_fail fail_guard
    {
        // Action function object
        [&] { ec = std::error_code(err, std::generic_category()); },
        // Condition function object
        boost::scope::check_error_code(err)
    };

    // ...
}
#include <boost/scope/defer.hpp>

The defer_guard scope guard is similar to scope_exit without a user-specified condition function object in terms of when it invokes the action function. But it does not support custom condition functions and lacks support for moveability and activation/deactivation - this scope guard is always active upon construction. This allows for a more efficient implementation when these features are not needed.

[Note] Note

defer_guard is a more lightweight version of scope_exit, similar to how std::lock_guard is a more lightweight version of std::unique_lock.

Since defer_guard effectively provides no interface to interact with after construction, it is better suited for anonymous "set up and forget" kind of scope guards. To emphasize this affinity, the library provides a BOOST_SCOPE_DEFER macro, which acts as a keyword defining a uniquely named defer_guard scope guard. The macro should be followed by the function object to be invoked on scope exit.

BOOST_SCOPE_DEFER []
{
    std::cout << "Hello world!" << std::endl;
};
[Note] Note

BOOST_SCOPE_DEFER requires support for C++17 CTAD. The defer_guard class itself is compatible with C++11, but given that there is no factory function for it, C++17 support is very much desired.

As you can see, BOOST_SCOPE_DEFER offers a few syntax improvements over the other scope guard declarations:

  • The declaration does not name a scope guard variable, meaning one does not need to invent one and there is no possibility to accidentally omit one or clash with other variables.
  • The declaration is generally shorter to type and easier to spot.
  • There are no extra parenthesis or curly braces around the function object.

However, it should be noted that the use of the BOOST_SCOPE_DEFER macro is entirely optional. Users are free to use defer_guard directly.

boost::scope::defer_guard guard{[]
{
    std::cout << "Hello world!" << std::endl;
}};

When using scope guards, users should make sure that all variables captured by reference are still in a valid state upon the scope guard destruction. This especially pertains to lambda functions that capture variables by reference by default (i.e. where the capture clause starts with &). Consider the following example:

void bad(std::unique_ptr< int > ptr)
{
    assert(ptr != nullptr);

    boost::scope::scope_exit guard{[&]
    {
        std::cout << "processed: " << *ptr << std::endl;
    }};

    process(std::move(ptr)); // may consume ptr, leaving it null for the scope guard action
}

Or a slightly less obvious version of it, involving implicit move of the returned value:

std::unique_ptr< int > bad(std::unique_ptr< int > ptr)
{
    assert(ptr != nullptr);

    boost::scope::scope_exit guard{[&]
    {
        std::cout << "processed: " << *ptr << std::endl;
    }};

    process(*ptr);

    return ptr; // moves from ptr, leaving it null for the scope guard action
}

In cases like this consider capturing by value in the function object or moving the scope guard to a deeper scope, so that it executes its action before the variables captured by reference become invalid.

std::unique_ptr< int > good(std::unique_ptr< int > ptr)
{
    assert(ptr != nullptr);

    {
        boost::scope::scope_exit guard{[&]
        {
            std::cout << "processed: " << *ptr << std::endl;
        }};

        process(*ptr);
    } // <- scope guard action runs here, before ptr is moved-from

    return ptr;
}

It is possible to use scope guard classes to implement scope exit actions that are initialized at run time. One way to do this is to use a function object wrapper such as std::function together with the scope guard to schedule the function call. For example:

using cleanup_func_t = std::function< void() >;
cleanup_func_t cleanup_func;
// Create an inactive scope guard first, since the cleanup function is not set yet
boost::scope::scope_exit< cleanup_func_t& > cleanup(cleanup_func, false);

// Later in the program, initialize the cleanup function with the function selected at run time
if (cond)
{
    cleanup_func = []
    {
        std::cout << "cond is true" << std::endl;
    };
}
else
{
    cleanup_func = []
    {
        std::cout << "cond is false" << std::endl;
    };
}

// Activate the scope guard once the cleanup function is initialized
cleanup.set_active(true);

It is also possible to do this with BOOST_SCOPE_DEFER, although it eliminates one of the advantages provided by this macro, namely not having to invent a variable name. Also note that the function wrapper must be valid at all times once the scope guard is constructed.

// Create a non-empty function wrapper that does nothing
std::function< void() > cleanup_func = [] {};
// Create a scope guard that refers to the function wrapper
BOOST_SCOPE_DEFER std::ref(cleanup_func);

// Later in the program, initialize the function wrapper
if (cond)
{
    cleanup_func = []
    {
        std::cout << "cond is true" << std::endl;
    };
}
else
{
    cleanup_func = []
    {
        std::cout << "cond is false" << std::endl;
    };
}

However, users should be aware that function wrappers typically use dynamic memory allocation internally and copy the function object data, which may involve calling copy constructors that may also fail with an exception. Although many standard library implementations use small object optimization for std::function, and this technique is also used in other implementations like Boost.Function, it is generally not guaranteed that initializing the function wrapper with a given function object will not throw. Because of this, using std::function and similar wrappers is usually not recommended.

If setting up the scope exit action needs to be a non-throwing operation (for example, if the scope guard is supposed to revert the effects of the immediately preceding operation), it is recommended to initialize inactive scope guards beforehand and only activate one of them at a later point in the program.

// Create inactive scope guards for both branches
boost::scope::scope_exit cleanup_true([]
{
    std::cout << "cond is true" << std::endl;
},
false);
boost::scope::scope_exit cleanup_false([]
{
    std::cout << "cond is false" << std::endl;
},
false);

// Later in the program, activate one of the scope guards.
// This won't throw.
if (cond)
    cleanup_true.set_active(true);
else
    cleanup_false.set_active(true);

Alternatively, one could implement the selection within the scope guard action itself.

// Create a single inactive scope guard that implements both branches
bool cond;
boost::scope::scope_exit cleanup([&cond]
{
    if (cond)
        std::cout << "cond is true" << std::endl;
    else
        std::cout << "cond is false" << std::endl;
},
false);

// Later in the program, select the branch to perform
// and activate the scope guard.
cond = select_branch();
cleanup.set_active(true);

Boost.ScopeExit defines a set of macros for defining code blocks to be executed at scope exit. Scope guards provided by Boost.Scope provide similar functionality, but with simpler syntax and new features. Differences between libraries are summarized in the table below.

Table 1.4. Boost.ScopeExit and Boost.Scope comparison

Feature

Boost.ScopeExit

Boost.Scope

Minimum and recommended C++ version.

C++03 minimum, C++11 recommended for some features.

C++11 minimum, C++17 recommended for some features.

Basic functionality.

BOOST_SCOPE_EXIT(void)
{
    std::cout << "Hello, World!" << std::endl;
}
BOOST_SCOPE_EXIT_END;
boost::scope::scope_exit guard([]
{
    std::cout << "Hello, World!" << std::endl;
});

Or:

BOOST_SCOPE_DEFER []
{
    std::cout << "Hello, World!" << std::endl;
};

Capturing variables.

Yes, both by value and by reference.

int x;
std::string str;
BOOST_SCOPE_EXIT(x, &str)
{
    std::cout << "x = " << x << ", str = " << str << std::endl;
}
BOOST_SCOPE_EXIT_END;

In C++03 mode without variadic macros support, Boost.Preprocessor sequence should be used to list captured variables.

Yes, by means of the standard C++ lambda captures.

int x;
std::string str;
boost::scope::scope_exit guard([x, &str]
{
    std::cout << "x = " << x << ", str = " << str << std::endl;
});

Or:

int x;
std::string str;
BOOST_SCOPE_DEFER [x, &str]
{
    std::cout << "x = " << x << ", str = " << str << std::endl;
};

Capturing all variables.

Yes, requires C++11.

int x;
std::string str;
BOOST_SCOPE_EXIT_ALL(&)
{
    std::cout << "x = " << x << ", str = " << str << std::endl;
};

Note that no BOOST_SCOPE_EXIT_END is used in this case. See below for BOOST_SCOPE_EXIT_ALL caveats.

Yes, by means of the standard C++ lambda captures.

int x;
std::string str;
boost::scope::scope_exit guard([&]
{
    std::cout << "x = " << x << ", str = " << str << std::endl;
});

Or:

int x;
std::string str;
BOOST_SCOPE_DEFER [&]
{
    std::cout << "x = " << x << ", str = " << str << std::endl;
};

Capturing this.

Yes, with a special this_ keyword.

class my_class
{
private:
    int x;
    std::string str;

public:
    void foo()
    {
        BOOST_SCOPE_EXIT(this_)
        {
            std::cout << "x = " << this_->x << ", str = " << this_->str << std::endl;
        }
        BOOST_SCOPE_EXIT_END;
    }
};

Note that the keyword must be explicitly used to reference members of the class within the scope exit block.

Capturing this is also possible using BOOST_SCOPE_EXIT_ALL (with the caveats described below).

class my_class
{
private:
    int x;
    std::string str;

public:
    void foo()
    {
        BOOST_SCOPE_EXIT_ALL(this)
        {
            std::cout << "x = " << x << ", str = " << str << std::endl;
        };
    }
};

Yes, by means of the standard C++ lambda captures.

class my_class
{
private:
    int x;
    std::string str;

public:
    void foo()
    {
        boost::scope::scope_exit guard([this]
        {
            std::cout << "x = " << x << ", str = " << str << std::endl;
        };
    }
};

Or:

class my_class
{
private:
    int x;
    std::string str;

public:
    void foo()
    {
        BOOST_SCOPE_DEFER [this]
        {
            std::cout << "x = " << x << ", str = " << str << std::endl;
        };
    }
};

Capturing specific members of a class.

Yes.

class my_class
{
private:
    int x;
    std::string str;

public:
    void foo()
    {
        BOOST_SCOPE_EXIT(x, &str)
        {
            std::cout << "x = " << x << ", str = " << str << std::endl;
        }
        BOOST_SCOPE_EXIT_END;
    }
};

No. Capture this or use lambda capture initializers (requires C++14) instead.

class my_class
{
private:
    int x;
    std::string str;

public:
    void foo()
    {
        BOOST_SCOPE_DEFER [x = x, str = std::ref(str)]
        {
            std::cout << "x = " << x << ", str = " << str.get() << std::endl;
        };
    }
};

Support for scope guards in templates.

Yes, using a special macro.

template< typename T >
void foo(T const& param)
{
    BOOST_SCOPE_EXIT_TPL(&param)
    {
        std::cout << "Param: " << param << std::endl;
    }
    BOOST_SCOPE_EXIT_END;
}

Yes, with no special syntax.

template< typename T >
void foo(T const& param)
{
    BOOST_SCOPE_DEFER [&param]
    {
        std::cout << "Param: " << param << std::endl;
    };
}

Support for variadic templates and argument packs.

Yes, using the BOOST_SCOPE_EXIT_ALL macro (see the caveats below).

template< typename... Args >
void foo(Args&&... args)
{
    BOOST_SCOPE_EXIT_ALL(&args...)
    {
        std::cout << "Params: " << boost::tuples::tie(args...) << std::endl;
    };
}

Yes, by means of the standard C++ lambda captures.

template< typename... Args >
void foo(Args&&... args)
{
    BOOST_SCOPE_DEFER [&args...]
    {
        std::cout << "Params: " << boost::tuples::tie(args...) << std::endl;
    };
}

Native support for activation/deactivation of the scope guard.

No, the activation/deactivation logic needs to be embedded into the action code block.

bool active = false;
BOOST_SCOPE_EXIT(&active)
{
    if (active)
        std::cout << "Hello, World!" << std::endl;
}
BOOST_SCOPE_EXIT_END;

active = true;

Yes, with scope_exit, scope_success and scope_fail. This includes the ability to delay activation of the initially inactive scope guards.

boost::scope::scope_exit guard([]
{
    std::cout << "Hello, World!" << std::endl;
},
false);

guard.set_active(true);

Support for custom conditions for executing the action.

No, the condition needs to be embedded into the action code block.

const auto uncaught_count = std::uncaught_exceptions();
BOOST_SCOPE_EXIT(uncaught_count)
{
    if (std::uncaught_exceptions() > uncaught_count)
        std::cout << "Exception thrown!" << std::endl;
}
BOOST_SCOPE_EXIT_END;

Yes, with scope_exit, scope_success and scope_fail.

boost::scope::scope_exit guard(
[]
{
    std::cout << "Exception thrown!" << std::endl;
},
[uncaught_count = std::uncaught_exceptions()]
{
    return std::uncaught_exceptions() > uncaught_count;
});

Or simply:

boost::scope::scope_fail guard([]
{
    std::cout << "Exception thrown!" << std::endl;
});

Utilities for checking exceptions and error codes as criteria for invoking the action.

No, the condition needs to be embedded into the action code block.

const unsigned int uncaught_count = boost::core::uncaught_exceptions();
BOOST_SCOPE_EXIT(uncaught_count)
{
    if (uncaught_count < boost::core::uncaught_exceptions())
        std::cout << "Exception thrown!" << std::endl;
}
BOOST_SCOPE_EXIT_END;

Or:

std::error_code ec;
BOOST_SCOPE_EXIT(&ec)
{
    if (ec)
        std::cout << "Execution failed!" << std::endl;
}
BOOST_SCOPE_EXIT_END;

Yes, exception_checker and error_code_checker.

// scope_fail by default checks for an exception
boost::scope::scope_fail guard([]
{
    std::cout << "Exception thrown!" << std::endl;
});

Or:

std::error_code ec;
boost::scope::scope_fail guard([]
{
    std::cout << "Execution failed!" << std::endl;
},
boost::scope::check_error_code(ec));

Named scope guards.

No, the scope guard is declared as an unnamed code block. (The scope guard is internally implemented as an object with a unique name on the stack, but this is not part of the public interface.)

Yes, scope guards are defined as objects. Also, scope_exit, scope_success and scope_fail are move-constructible and can be returned from functions.

Anonymous scope guards.

Yes.

Yes, using the BOOST_SCOPE_DEFER macro.

Macro-less usage.

No.

Yes.


In the table above, you may notice that a significant amount of feature parity between Boost.ScopeExit and Boost.Scope is achieved by using the BOOST_SCOPE_EXIT_ALL macro in the former. This macro is implemented using C++11 lambda functions internally, which explains a lot of similarities between the libraries in this case. Users can also define BOOST_SCOPE_EXIT_CONFIG_USE_LAMBDAS to force other Boost.ScopeExit macros to use C++11 lambdas internally, which will change the syntax and variable capture rules accordingly. However, there are a few caveats one needs to keep in mind when using the library in this mode (or the BOOST_SCOPE_EXIT_ALL macro in any mode):

  • Since it relies on C++11 lambda functions, it is not available in C++03 mode.
  • Boost.ScopeExit scope guards that are implemented using C++11 lambdas store the lambda function in a boost::function object internally. This means that creating the scope guard may require dynamic memory allocation and can fail with an exception. Boost.ScopeExit does not document behavior in this case, but in practice, as of Boost 1.83.0, the scope exit block does not get executed in this case. This means that Boost.ScopeExit in this mode cannot be safely used in non-throwing contexts, and cannot guarantee execution of the scope exit block. There is also performance overhead associated both with construction and execution of the scope guard.

In comparison, Boost.Scope scope guards do not use function object wrappers, such as boost::function or std::function, to store the function objects. This means that the scope guards do not perform dynamic memory allocation (unless the function objects themselves do) and do not have the associated exception safety issues and performance overhead. Additionally, the scope guards will execute the action if initializing the scope guard fails with an exception.

The C++ Extensions for Library Fundamentals TS defines three scope guard types, scope_exit, scope_success and scope_fail, which have similar semantics to the same-named scope guards provided by this library. There are some differences and extensions that are summarised below.

All three of the scope_exit, scope_success and scope_fail scope guards provided by Boost.Scope support an optional condition function that is used to decide whether the scope guard should invoke its wrapped action function. The scope guards defined by the TS do not have this feature, the behavior of each of the three scope guards is predefined by the specification.

The default condition functions used in the Boost.Scope scope guards make the behavior equivalent to the TS, so if the user doesn't use custom condition functions, the scope guard usage is equivalent between the two. If custom condition functions are used, those can be emulated in TS scope guards by embedding the condition function into the action called by std::experimental::scope_exit. For example, if the user's code uses error codes as the failure indication with scope_fail, the equivalent TS code could look like this:

Table 1.5. Using custom condition functions

Boost.Scope

Library Fundamentals TS

std::error_code ec;
boost::scope::scope_fail guard
{
    [] { handle_error(); },
    boost::scope::check_error_code(ec)
};
std::error_code ec;
std::experimental::scope_exit guard
{
    [&ec]
    {
        if (ec)
            handle_error();
    }
};

Note that we had to switch from scope_fail to scope_exit in case of TS.

Scope guards defined in the TS are always constructed active and can only be deactivated by calling release() method. Boost.Scope supports creating initially inactive scope guards, as well as activating and deactivating them after creation. This allows for more flexible placement of the scope guards in the user's code, which is useful if the decision on whether to execute the scope guard action needs to be made after the scope guard is constructed.

This feature can be emulated in TS using additional state that the scope guard action checks to see if the action needs to be performed.

Table 1.6. Explicitly activating scope guards

Boost.Scope

Library Fundamentals TS

// Insert value into both containers and call on_inserted() afterwards.
// pv may be null, in which case only insert the value into the set.
// Maintain both containers intact if an exception is thrown.
void insert_two(std::vector< int >* pv, std::set< int >& set, int value)
{
    // Initially inactive scope guard
    boost::scope::scope_fail pv_rollback([pv] { pv->pop_back(); }, false);
    if (pv)
    {
        pv->push_back(value); // may throw
        pv_rollback.set_active(true); // activate the scope guard
    }

    auto [it, inserted] = set.insert(value); // may throw
    // Initially active scope guard, if a new element was inserted, otherwise inactive
    boost::scope::scope_fail set_rollback([&set, &it] { set.erase(it); }, inserted);

    on_inserted(); // may throw
}
// Insert value into both containers and call on_inserted() afterwards.
// pv may be null, in which case only insert the value into the set.
// Maintain both containers intact if an exception is thrown.
void insert_two(std::vector< int >* pv, std::set< int >& set, int value)
{
    bool pv_inserted = false;
    // Initially active scope guard, but blocked by pv_inserted == false
    std::experimental::scope_exit pv_rollback([pv, &pv_inserted]
    {
        if (pv_inserted)
            pv->pop_back();
    });
    if (pv)
    {
        pv->push_back(value); // may throw
        pv_inserted = true; // unblock the scope guard
    }

    auto [it, inserted] = set.insert(value); // may throw
    // Initially active scope guard, which we immediately deactivate if no element was inserted
    std::experimental::scope_exit set_rollback([&set, &it] { set.erase(it); });
    if (!inserted)
        set_rollback.release();

    on_inserted(); // may throw
}

Note that Boost.Scope guards don't provide release() member function and instead provide set_active() with a bool argument, where release() is equivalent to set_active(false). This allows for both activating and deactivating the scope guard. The bool argument is more convenient than two distinct methods for activating and deactivating the scope guard when the indication of whether the scope guard needs to be active is expressed as a run time value. For example, the insertion into set above could be written like this:

std::set< int >::iterator it;
// Initially create an inactive scope guard to revert the insertion
boost::scope::scope_fail set_rollback([&set, &it] { set.erase(it); }, false);

// Try insering a new element into the set
bool inserted;
std::tie(it, inserted) = set.insert(value); // may throw

// Activate the scope guard only if the element was inserted
set_rollback.set_active(inserted);
[Note] Note

At least one person during the Boost.Scope review noted that release() is a poor name in the context of scope guards. This, and the fact that a method with a bool argument is more flexible and provides the same functionality, is why Boost.Scope does not provide release().

In addition to set_active(), Boost.Scope scope guards can be queried whether they are active via the active() method. This feature has no equivalent in the TS.

In the Library Fundamentals TS, only scope_success action is allowed to throw exceptions, while scope_fail and scope_exit destructors are unconditionally marked noexcept. Since the TS does not support custom condition functions, scope_fail can only invoke its action while an exception is unwinding the stack, so throwing an exception from the scope guard action would cause the program to terminate. In case of scope_exit the justification is less definitive, as this scope guard may invoke its action when no exception is in flight. However, the situation when the action is called due to an exception is still possible, and the TS takes a conservative approach by prohibiting the action to throw exceptions.

Boost.Scope takes a different approach. Since all three scope guards support custom condition functions, each of them may invoke its action whether or not an exception is propagating through the stack. The notion of "failure" is no longer equivalent to "exception", so even for a scope_fail scope guard with a custom failure condition it may make sense to allow throwing exceptions. Therefore, for all scope guards provided by Boost.Scope, their destructors are noexcept only if their action and condition functions are both marked noexcept. It is user's responsibity to ensure that the scope guard actions and conditions do not throw exceptions when another exception is in flight. The user may even perform a runtime check with std::uncaught_exceptions, if needed. If the user is willing to enforce the non-throwing guarantee for his scope guards, he is able to do this by marking his action functions noexcept:

boost::scope::scope_exit guard([&]() noexcept { /* I promise I won't throw */ });

While this allows more scope guards to throw exceptions, it makes no difference in behavior when the action throws during an exception propagation. Whether the scope guard's destructor is marked noexcept or not, the program will terminate either way in this case.

This Boost.Scope feature cannot be emulated with TS scope guards (other than when the use case fits scope_success).


PrevUpHomeNext