Cだとgotoをつかうのが基本。C++だとtry/throw/catchもあり。 めんどくさいのが、atomicオペレーションを実装するときに、内部で副作用があちこちに発生して 途中で失敗したら取消てまわらないといけない場合。 正常系はこんなかんじだとして
int
main(int ac, char **av)
{
    int rc = 1;
    int x = av[1] ? atoi(av[1]) : 0; /*atoiのエラーはみぬふり*/
    f(x); /*エラーをおこすかも*/
    g(x); /*エラーをおこすかも*/
    return 0;
}

Cのgotoパターン。
#include <iostream>
#include <cstdlib>

using std::cout;
using std::endl;

int a = 0;
int b = 0;
void
show()
{
    cout << "a=" << a << endl
         << "b=" << b << endl;
}

int
f(int x)
{
    if (x > 100)
        return -1;
    a += x;
    return 0;
}
void
undo_f(int x)
{
    a -= x;
}

int
g(int x)
{
    if (x > 10)
        return -1;
    b += x;
    return 0;
}

int
main(int ac, char **av)
{
    int rc = 1;
    int x = av[1] ? atoi(av[1]) : 0;
    if (f(x) < 0)
        goto err_f;
    if (g(x) < 0)
        goto err_g;
    rc = 0;
    goto out;

 err_g:
    undo_f(x);
 err_f:
 out:
    show();
    cout << "rc=" << rc << endl;
    return rc;
}
g(x)が失敗したf(x)を取り消すためにundo_f(x)を呼び出す必要がある。 副作用をおこすブロック(この例だとf()とかg())毎に対応する gotoラベルを用意してリカバリルーチンを逆順に呼んでいく。 問題はf(),g()の部分とリカバリの部分がコード上で離れてしまうのと 逆順に書くの人間がやらないといけないところ。f()とg()の順序が逆になったときや f()とg()の間にh()が入ったときにリカバリ部分も書き換えないといけない。 たぶん書き換えるのは忘れないとおもうが、実装ミスをする個所が増える。
C++の例外処理をつかった例
#include <iostream>
#include <cstdlib>

using std::cout;
using std::endl;

int a = 0;
int b = 0;
void
show()
{
    cout << "a=" << a << endl
         << "b=" << b << endl;
}

struct ERR {};

void
f(int x)
{
    if (x > 100)
        throw ERR();
    a += x;
}
void
undo_f(int x)
{
    a -= x;
}

void
g(int x)
{
    if (x > 10)
        throw ERR();
    b += x;
}

int
main(int ac, char **av)
{
    int rc = 1;
    int x = av[1] ? atoi(av[1]) : 0;
    try {
        f(x);
        try {
            g(x);
            rc = 0;
        }
        catch (ERR&) {
            undo_f(x);
        }
    }
    catch (ERR&) {
    }
    show();
    cout << "rc=" << rc << endl;
    return rc;
}
tryブロックの入れ子で実装している。gotoパターンに比べて、正常系の処理とエラーリカバリの処理とが 構文上のブロックとして関連づけられるのでミスが減るが、外のtryブロックほどcatchブロックが離れてしまうので コード上の見通しがわるくなる。
おなじく例外処理をつかう別の例
#include <iostream>
#include <stdexcept>
#include <cstdlib>

using std::cout;
using std::endl;
using std::exception;
using std::runtime_error;

int a = 0;
int b = 0;
void
show()
{
    cout << "a=" << a << endl
         << "b=" << b << endl;
}

struct ERR {};

void
f(int x)
{
    if (x > 100)
        throw ERR();
    a += x;
}
void
undo_f(int x)
{
    a -= x;
}

void
g(int x)
{
    if (x > 10)
        throw ERR();
    b += x;
}

void
proc2(int x)
{
    try {
        g(x);
    }
    catch (ERR&) {
        throw;
    }
}

void
proc1(int x)
{
    f(x);
    try {
        proc2(x);
    }
    catch (ERR&) {
        undo_f(x);
        throw;
    }
}

int
main(int ac, char **av)
{
    int rc = 1;
    int x = av[1] ? atoi(av[1]) : 0;
    try {
        proc1(x);
        rc = 0;
    }
    catch (ERR&) {
    }
    show();
    cout << "rc=" << rc << endl;
    return rc;
}
1関数1処理に限定するように書くことで構文上のtryの入れ子を避けている。 呼び出し関係から実際にはtryは入れ子になっている。
C++0xで入るlambdaをつかった例
#include <iostream>
#include <stdexcept>
#include <cstdlib>

using std::cout;
using std::endl;
using std::exception;
using std::runtime_error;

int a = 0;
int b = 0;
void
show()
{
    cout << "a=" << a << endl
         << "b=" << b << endl;
}

struct ERR {};

void
f(int x)
{
    if (x > 100)
        throw ERR();
    a += x;
}
void
undo_f(int x)
{
    a -= x;
}

void
g(int x)
{
    if (x > 10)
        throw ERR();
    b += x;
}

template <typename F>
struct cmd
{
    F* pf;
    cmd(F& f) : pf(&f) {}
    ~cmd() { if (pf) (*pf)(); }
    void clear() { pf = 0; }
};
template <typename F>
cmd<F>
make_cmd(F f)
{
    return cmd<F>(f);
}

int
main(int ac, char **av)
{
    int rc = 1;
    int x = av[1] ? atoi(av[1]) : 0;
    try {
        f(x);
        auto uf (make_cmd([&x]() { undo_f(x); }));
        g(x);
        uf.clear(); //gが成功したらfのundoは不要。
        rc = 0;
    }
    catch (ERR&) {
    }
    show();
    cout << "rc=" << rc << endl;
    return rc;
}