std::call_once(3)
π NAME π
std::call_once β bestie, it's std::call_once, that's literally the whole name β¨ (yes the manpage really just names it after itself, iconic behavior honestly)
π¦ SYNOPSIS π¦
Okay so first things first, it lives in the header <mutex> π«Ά
template< class Callable, class... Args >
void call_once( std::once_flag& flag, Callable&& f, Args&&... args );
(since C++11) β yeah she's a modern girlie π
What she does: executes the Callable object f exactly once, even if called concurrently from several threads. Like, one time only, no cap. π₯
πΈ In detail: πΈ
- If, by the time
std::call_onceis called,flagindicates thatfwas already called,std::call_oncereturns right away (such a call tostd::call_onceis known as passive). She just peaces out, no notes. βοΈ - Otherwise,
std::call_oncecallsINVOKE(std::forward<Callable>(f), std::forward<Args>(args)...). Unlike thestd::threadconstructor orstd::async, the arguments are not moved or copied because they do not need to be transferred to another thread of execution (such a call tostd::call_onceis known as active). Periodt, no unnecessary copying around here. π - If that invocation throws an exception, it is propagated to the caller of
std::call_once, andflagis not flipped so that another call will be attempted (such a call tostd::call_onceis known as exceptional). She said "try again bestie", how forgiving of her π - If that invocation returns normally (such a call to
std::call_onceis known as returning),flagis flipped, and all other calls tostd::call_oncewith the sameflagare guaranteed to be passive. Done and dusted β
All active calls on the same flag form a single total order consisting of zero or more exceptional calls, followed by one returning call. The end of each active call synchronizes-with the next active call in that order. It's giving queued up in the group chat, everybody waits their turn π
The return from the returning call synchronizes-with the returns from all passive calls on the same flag: this means that all concurrent calls to std::call_once are guaranteed to observe any side-effects made by the active call, with no additional synchronization. No extra work needed, she did it for you π§
π PARAMETERS π
flagβ an object, for which exactly one function gets executed. One flag, one function, that's the whole deal πfβ Callable object to invoke. The main character of this whole operation β¨args...β arguments to pass to the function. Her little entourage π«Ά
β RETURN VALUE β
(none)
Like, literally nothing. She does the thing and leaves. π
π EXCEPTIONS π
std::system_errorif any condition prevents calls tostd::call_oncefrom executing as specified. Rude of the system, honestly.- Any exception thrown by
f. Whatever she throws, you're catching it fr fr.
π NOTES π
If concurrent calls to std::call_once pass different functions f, it is unspecified which f will be called. Not me picking a favorite π The selected function runs in the same thread as the std::call_once invocation it was passed to.
Initialization of function-local statics is guaranteed to occur only once even when called from multiple threads, and may be more efficient than the equivalent code using std::call_once. Slay, the compiler already had this handled π
The POSIX equivalent of this function is pthread_once. Old school icon, we respect her π
π₯ EXAMPLE π₯
// Run this code
#include <iostream>
#include <mutex>
#include <thread>
std::once_flag flag1, flag2;
void simple_do_once()
{
std::call_once(flag1, [](){ std::cout << "Simple example: called once\n"; });
}
void may_throw_function(bool do_throw)
{
if (do_throw)
{
std::cout << "Throw: call_once will retry\n"; // this may appear more than once
throw std::exception();
}
std::cout << "Did not throw, call_once will not attempt again\n"; // guaranteed once
}
void do_once(bool do_throw)
{
try
{
std::call_once(flag2, may_throw_function, do_throw);
}
catch (...) {}
}
int main()
{
std::thread st1(simple_do_once);
std::thread st2(simple_do_once);
std::thread st3(simple_do_once);
std::thread st4(simple_do_once);
st1.join();
st2.join();
st3.join();
st4.join();
std::thread t1(do_once, true);
std::thread t2(do_once, true);
std::thread t3(do_once, false);
std::thread t4(do_once, true);
t1.join();
t2.join();
t3.join();
t4.join();
}
π Possible output: π
Simple example: called once
Throw: call_once will retry
Throw: call_once will retry
Throw: call_once will retry
Did not throw, call_once will not attempt again
Lowkey obsessed with how the "Throw:" line shows up three times but the last one is guaranteed exactly once. That's the whole personality of this function right there π
π Defect reports π
The following behavior-changing defect reports were applied retroactively to previously published C++ standards. History was rewritten, and honestly? She ate. β¨
- LWG 2080 β applied to C++11. Behavior as published:
std::invalid_argumentwould be thrown iffis invalid, but the scenario wherefis invalidated is not specified. Correct behavior: removed this error condition. - LWG 2442 β applied to C++11. Behavior as published: the arguments were copied and/or moved before invocation. Correct behavior: no copying/moving is performed.
π¦ SEE ALSO π¦
once_flagβ helper object to ensure thatcall_onceinvokes the function only once (C++11) (class)- C documentation for
call_once
and that's the whole page bestie, go forth and call things exactly once πβ¨ππ¦πΈπ π«Ά
Source and date for the record, since we're being official: http://cppreference.com Β· 2024.06.10 Β· std::call_once(3) β mwah, bye! ππ§
π¦ where this page comes from
- nixpkgs unstable: stdman 2024.07.05
- opensuse-leap 16.0: stdman 2024.07.05-bp160.1.9 Β· MIT
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash-1017 on : a language model's rewrite of the original, under its terms