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

Click here to view the latest version of this page.
PrevUpHomeNext

Tutorial

Imagine that you want to make many modifications to data members of the World class in the World::addPerson function. You start with adding a new Person object to a vector of persons:

void World::addPerson(Person const& person) {
    bool commit = false;
    m_persons.push_back(person);  // (1) direct action

Some operation down the road may throw an exception and all changes to involved objects should be rolled back. This all-or-nothing semantic is also known as strong guarantee.

In particular, last added person must be deleted from m_persons when the function throws. All you need is to define a delayed action (release of a resource) right after the direct action (resource acquisition):

void World::addPerson(Person const& aPerson) {
    bool commit = false;
    m_persons.push_back(aPerson);  // (1) direct action
    BOOST_SCOPE_EXIT( (&commit)(&m_persons) )
    {
        if(!commit)
            m_persons.pop_back(); // (2) rollback action
    } BOOST_SCOPE_EXIT_END

    // ...                        // (3) other operations
    
    commit = true;                // (4) turn all rollback actions into no-op
}

The block below point (1) is a ScopeExit declaration. Unlike point (1), an execution of the ScopeExit body will be delayed until the end of the current scope. In this case it will be executed either after point (4) or on any exception.

The ScopeExit declaration starts with BOOST_SCOPE_EXIT macro invocation which accepts Boost.Preprocessor sequence of captured variables. If a capture starts with the ampersand sign &, a reference to the captured variable will be available inside the ScopeExit body; otherwise, a copy of the variable will be made after the point (1) and only the copy will be available inside the body.

In the example above, variables commit and m_persons are passed by reference because the final value of the commit variable should be used to determine whether to execute rollback action or not and the action should modify the m_persons object, not its copy. This is a most common case but passing a variable by value is sometimes useful as well.

Consider a more complex case where World::addPerson can save intermediate states at some points and roll back to the last saved state. You can use Person::m_evolution to store a version of changes and increment it to cancel all rollback actions associated with those changes.

If you pass a current value of m_evolution stored in the checkpoint variable by value, it will remain unchanged until the end of aa scope and you can compare it with the final value of the m_evolution. If the latter wasn't incremented since you saved it, the rollback action inside the block should be executed:

void World::addPerson(Person const& aPerson) {
    m_persons.push_back(aPerson);

    // This block must be no-throw
    Person& person = m_persons.back();
    Person::evolution_t checkpoint = person.m_evolution;

    BOOST_SCOPE_EXIT( (checkpoint)(&person)(&m_persons) )
    {
        if(checkpoint == person.m_evolution)
            m_persons.pop_back();
    } BOOST_SCOPE_EXIT_END

    // ...

    checkpoint = ++person.m_evolution;

    // Assign new id to the person
    World::id_t const prev_id = person.m_id;
    person.m_id = m_next_id++;
    BOOST_SCOPE_EXIT( (checkpoint)(&person)(&m_next_id)(prev_id) )
    {
        if(checkpoint == person.m_evolution) {
            m_next_id = person.m_id;
            person.m_id = prev_id;
        }
    } BOOST_SCOPE_EXIT_END

    // ...

    checkpoint = ++person.m_evolution;
}

Full code listing can be found in world.cpp.


PrevUpHomeNext