C++ Basics: Retry Initialization after std::call_once Throws
Distinguish an initialization attempt from the one successful execution recorded by a once_flag.
Prepare a scratch directory
Tested on Linux x86_64 with GCC 14.2.0. Install a C++20-capable GCC compiler and its standard-library development files, with g++ on PATH. You also need Bash, a text editor, and mktemp, rm, and rmdir. No third-party library is required.
Keep the same terminal open. Stop if setup fails before saving the source:
start_directory="$PWD"
work_directory="$(mktemp -d)" || exit 1
cd -- "$work_directory" || exit 1Run the example
Save this as call-once-retry-demo.cpp:
#include <iostream>
#include <mutex>
#include <stdexcept>
int main()
{
std::once_flag initialization_flag;
int attempts = 0;
int configuration = 0;
const auto initialize = [&] {
++attempts;
if (attempts == 1) {
throw std::runtime_error("temporary initialization failure");
}
configuration = 42;
};
try {
std::call_once(initialization_flag, initialize);
} catch (const std::runtime_error& error) {
std::cout << "First attempt: " << error.what() << '\n';
}
std::call_once(initialization_flag, initialize);
std::call_once(initialization_flag, initialize);
std::cout << "Attempts: " << attempts << '\n';
std::cout << "Configuration: " << configuration << '\n';
}Compile and run it. The second command runs only after compilation succeeds:
g++ -std=c++20 -Wall -Wextra -Werror -pedantic -pthread call-once-retry-demo.cpp -o call-once-retry-demo &&
./call-once-retry-demoObserved output:
First attempt: temporary initialization failure
Attempts: 2
Configuration: 42Read the result
The first call propagates the exception and does not mark initialization successful. The second call executes initialize again and returns normally. The third call skips it. Three calls therefore produce two attempts and one successful initialization. The calls are sequential here to keep the retry sequence easy to inspect.
Apply it carefully
Concurrent callers using the same flag are coordinated by call_once, but the flag does not roll back side effects from a failed attempt. Make retries safe and catch failures where the application can recover. There is no reset operation on once_flag. If you need replaceable configuration, use a different lifetime and synchronization design.
The C++ working draft documents the relevant rules. This example uses C++20 facilities; the current draft also contains newer additions.
Clean up
Remove only the source and executable created here, then remove the empty practice directory:
rm -- call-once-retry-demo.cpp call-once-retry-demo
cd -- "$start_directory" || exit 1
rmdir -- "$work_directory"
unset start_directory work_directoryIf the directory is not empty, inspect its remaining contents before removing anything else.
Acknowledgements
This post was created with assistance from AI.