What can I do if I want to throw a C++ exception from my InitOnce callback?


Suppose you want to use the Init­Once­Execute­Once function to perform one-time initialization, but your initialization function might throw a C++ exception. We know that this is not allowed because you don't control all the frames the exception is being thrown across, so what are your options?

The naïve solution is to catch the exception before it escapes your callback, and then rethrow it on the other side.

void Sample()
{
    struct State {
        std::exception_ptr p;
        // other members you want to access from the lambda
    } state;
    if (!InitOnceExecuteOnce(&g_InitOnce,
          [](PINIT_ONCE InitOnce, void* Parameter, void** Context)
          -> BOOL
          {
              auto s = reinterpret_cast<State*>(Parameter);
              try {
                  init_stuff();
                  return TRUE;
              } catch (std::exception& e) {
                  s->p = std::current_exception();
                  return FALSE;
              }
          }, &state, nullptr)) {
        // Failed due to exception.  Rethrow now that we are
        // safely outside the InitOnceExecuteOnce function.
        std::rethrow_exception(state.p);
    }
}

A less cumbersome solution is to use synchronous two-phase initialization:

void Sample()
{
    void* result;
    BOOL pending;
    if (!InitOnceBeginInitialize(&g_InitOnce, 0,
                                 &pending, &result)) {
        if (pending) {
            try {
                init_stuff();
            } catch (...) {
                InitOnceComplete(&g_InitOnce,
                                 INIT_ONCE_INIT_FAILED, result);
                throw;
            }
            InitOnceComplete(&g_InitOnce, 0, result);
        }
    }
}

Synchronous two-phase initialization performs the initialization inline rather than in a callback, which saves you the trouble of having to save the exception in one place and rethrow it in another place. You can just tell the InitOnce engine that initialization failed and then allow the exception to propagate.

You might decide to wrap this pattern inside an RAII type.

class InitOnceGuard
{
public:
    InitOnceGuard(PINIT_ONCE initOnce) :
        m_initOnce(initOnce),
        m_success(InitOnceBeginInitialize(initOnce, 0, &m_pending, nullptr)),
        m_status(INIT_ONCE_INIT_FAILED)
    {
    }

    ~InitOnceGuard()
    {
        if (NeedInitialization()) InitOnceComplete(m_initOnce, m_status, nullptr);
    }

    InitOnceGuard(const InitOnceGuard&) = delete;
    InitOnceGuard(InitOnceGuard&&) = delete;
    InitOnceGuard& operator=(const InitOnceGuard&) = delete;
    InitOnceGuard& operator=(const InitOnceGuard&&) = delete;

    bool NeedInitialization() { return m_success && m_pending; }

    // If you don't Complete, then the guard assumes that initialization failed.
    void Complete() { m_status = 0; }

private:
    PINIT_ONCE m_initOnce;
    bool m_success;
    bool m_pending;
    DWORD m_status;
}

void Sample()
{
    InitOnceGuard guard(&g_InitOnce);
    if (guard.NeedInitialization()) {
        init_stuff();
        guard.Complete();
    }
}

If the guard destructs without ever being Completed, either because of an exception, or because the caller decided that initialization failed in an unexceptional way, then the destructor will tell the InitOnce engine that initialization failed. This will unblock any other threads that are waiting for initialization to complete and allow them to give it a try.

If the guard is Completed, then its destructor tells the InitOnce engine that initialization was successful.

After thinking about all that, you might realize that the fact that you're throwing C++ exceptions means that you're already committed to C++, so you may as well go all in: Use std::call_once or C++ static locals. These are part of the C++ standard and are fully exception-aware. Of course, it assumes that all the frames you are throwing across came from the same C++ compiler.

Comments (5)
  1. Cesar says:

    > Use std::call_once or C++ static locals.

    Be careful with C++ static locals: from what I recall, their initialization isn’t thread-safe in MSVC.

    1. David Haim says:

      It is after c++11.

    2. Darran Rowe says:

      This was added to VC in VC2015.

  2. Alex Guteniev says:

    std::call_once is implemented via InitOnceExecuteOnce in VS2015.
    I guess this means that you can just throw from InitOnceExecuteOnce parameter, if you want to use InitOnceExecuteOnce.

    1. Ah, but notice that std::call_once wraps the lambda inside a try/catch before calling InitOnceExecuteOnce (_TRY_BEGIN_CATCH_ALL) and rethrows the exception after InitOnceExecuteOnce returns (_XSTD rethrow_exception). So the evidence is the opposite of what you claim.

Comments are closed.

Skip to main content