FREE Subscription to Dr. Dobb’s Digest: Same Great Content, New Digital Edition
Site Archive (Complete)
C++
Email
Print
Reprint

add to:
Del.icio.us
Digg
Google
Furl
Slashdot
Y! MyWeb
Blink
November 06, 2007
Transactional Programming

(Page 1 of 7)
Calum Grant
Database techniques applied to C++ programming.
Calum, who holds a Ph.D. in Computer Science from the University of Cambridge, is a senior software engineer at Sophos Plc. He can be contacted at calum.grant@sophos.com.


When programs encounter exceptions, they must not leave data in an inconsistent state. It would be no good for a function to half-complete because that could leave the data in an unexpected state and lead to bugs later in the program. But this is exactly what can happen when exceptions are thrown halfway through a function.

For example, the problem with this function:

std::vector<std::string> list1, list2; void f(const std::string &s) { list1.push_back(s); list2.push_back(s); }

is that the second push_back() might fail and throw std::bad_alloc. This would leave the program in an inconsistent state because list1 and list2 are different. While desktop computers rarely deny a memory request, std::bad_alloc should be anticipated on portable devices or servers.

In an ideal world, functions should either completely succeed or fail without side effects. This all-or-nothing behavior is referred to as "atomic" because it will never leave the program in a halfway state.

A way of describing the effects of exceptions on functions is with Abrahams guarantees [1]:

  • Basic Guarantee. If an exception is thrown, no resources are leaked and objects remain in a destructible and usable—but not necessarily predictable—state.
  • Strong Guarantee. If an exception is thrown, the program state remains unchanged.
  • Nothrow Guarantee. The function will not emit an exception under any circumstances.

Although the nothrow guarantee sounds ideal, it is often impossible to arrange for a function to never throw an exception, especially when memory allocation takes place, which could throw std::bad_alloc at an inopportune moment. Many standard library containers rely on memory allocation and could, therefore, throw. The basic guarantee is often not good enough, since leaving a program in a safe but indeterminate state is not terribly useful. It is much better to live with exceptions, and let them propagate out to the caller in a safe and predictable way.

But error recovery is not straightforward in C++. When an exception is thrown, all of the side effects prior to the exception must be undone. In real code this is not often done, or it is added as an afterthought. Even when exceptions are considered up front, analyzing them and recovering from them is difficult and error prone. Exceptional paths are generally the least tested part of software—it is impractical to unit-test every exceptional path caused by std::bad_alloc.

While the best solution is to try to redesign the data structures, this isn't always possible. Consequently, you might try to catch the second exception and undo the push_back() on list1:

void f(const std::string &s) { list1.push_back(s); try { list2.push_back(s); } catch(...) { list1.pop_back(); throw; } }

This code is safe because the Standard guarantees that pop_back() will not throw an exception. Unfortunately, it is also ugly. In more complex situations, wrapping individual operations in a try-catch is hard work and makes the code difficult to read.

Scope-guards [2] offer an alternative. A scope-guard is a utility class that performs some important clean-up in its destructor, as in:

void f(const std::string &s)
{
  list1.push_back(s);
  ScopeGuard guard = MakeObjGuard(
   list1, &std::vector<std::string>::pop_back);
  list2.push_back(s);
  guard.Dismiss();
}

guard calls list1.pop_back() in its destructor, unless guard.Dismiss() is called. So the scope-guard automatically undoes list1.push_back() if list2.push_back() fails. This is an improvement because there is no need to write out the exception handler.

Unfortunately, while you can safely undo a push_back(), you cannot safely undo a deletion or a push_front() because an exception could be thrown while trying to clean up after the first exception. For example:

void g() { list.pop_back(); h(list); }

You could not write:

void g()
{
 std::string saved_back = 
    list.back();
 list.pop_back();
 try
 {
  h(list);
 }
 catch(...)
 {
  list.push_back(saved_back);
                  // Can throw
  throw;
 }
}

because list.push_back() could itself fail, breaking our guarantee of atomic behavior.

This shows that only certain operations can be undone on the standard containers. Atomic behavior is only possible to guarantee when a function has no side effects prior to an exception being thrown, or the availability of a guaranteed undo function that can be called in a scope-guard or an exception handler.

Dealing with exceptions is complicated and sometimes messy, and requires different approaches in different circumstances. Some good examples of the subtleties of exception handling are given by Herb Sutter both in Exceptional C++: 47 Engineering Puzzles, Programming Problems and Solutions (Addison-Wesley, 2000); and More Exceptional C++ (Addison-Wesley, 2002).

1 Transactional Programming | 2 Transactions | 3 Nesting Transactions | 4 Transactional Values | 5 Implementation | 6 Lists and Trees | 7 Cleanup, Concurrent Access, Performance Next Page
TOP 5 ARTICLES
No Top Articles.
DR. DOBB'S CAREER CENTER
Looking for a new job? open | close
Search jobs on Dr. Dobb's TechCareers
Function:

Keyword(s):

State:  
  • Post Your Resume
  • Employers Area
  • News & Features
  • Blogs & Forums
  • Career Resources

    Browse By:
    Location | Employer | City
  • Most Recent Posts:



    MICROSITES
    FEATURED TOPIC

    ADDITIONAL TOPICS

    INFO-LINK



     




    Techweb
    Informationweek Business Technology Network
    InformationweekInformationweek 500Informationweek 500 ConferenceInformationweek AnalyticsInformationweek Events
    Informationweek MagazineGlobal CIOIWK Government ITbMightyByte and SwitchDark Reading
    Digital LibraryIntelligent EnterpriseInternet EvolutionNetwork ComputingPlug Into The CloudDr. DobbsContentinople
    space
    TechWeb Events Network
    InteropVoiceConWeb 2.0 ExpoWeb 2.0 SummitEnterprise 2.0Mobile Business ExpoNoJitter
    Black HatGTECEnergy CampCloud ConnectGov 2.0 ExpoGov 2.0 Summit
    space
    Light Reading Communications Network
    Light ReadingLight Reading AsiaUnstrungCable Digital NewsInternet EvolutionPyramid Research
    Heavy ReadingLight Reading LiveLight Reading InsiderEthrnet ExpoTelco TVTower Technology Summit
    space
    Financial Technology Network
    Advanced TradingBank Systems and TechnologyInsurance and TechnologyWall Street and TechnologyAccelerating WallstreetBST SummitBuyside Trading SummitIT Summit
    space
    Microsoft Technology Network
    MSDNTechNetTotal IT ProTotal Dev ProNET Total Dev Pro CommunitySQL Total Dev Pro Community
    space