Edit: this is an XY Problem but keep the original title as well. It might help other people having the same XY problem. The goal should be: "have std::once_flag
flipped only after the task is successfully finished."
How do I not using g_init
(in the below sample) while avoiding the filesystem::exists()
check after initialization?
cfg["PATH"]
is unknown and it can only be supplied by caller.Welcome C++20 or C++23 and other solutions. It's good to be standard or homemade solution. Also it might be an XY problem...
A simplified demo
#include <mutex>
#include <filesystem>
#include <atomic>
#include "fmt/core.h"
#include "nlohmann/json.hpp"
using json = nlohmann::json;
std::once_flag g_flag;
std::atomic<bool> g_init{false};
void doOnce(std::string path){
// initializing from file...
fmt::print("initialization done");
}
void doWork(json& cfg){
// Atomic flag for
if(g_init) {
fmt::print("already inited");
return;
}
std::string path = cfg["PATH"];
if(!std::filesystem::exists(path)){
fmt::print("load failed");
return;
}
std::call_once(g_flag, doOnce, path);
}
int main() {
json cfg;
cfg["PATH"] = "/opt/usr/foo";
// In the real world case, caller will call it
// with random values and PATH might not exist.
doWork(cfg);
}
It seems that std::once_flag
doesn't expose anything to know whether it's called.
This is exactly the problem call_once
is supposed to solve. If you let it do it's job it'll handle all this for you. If you throw an exception when initialisation fails call_once
will run again:
void doOnce(std::string path){
if(!std::filesystem::exists(path)){
throw std::runtime_error("load failed");
}
// initializing from file...
fmt::print("initialization done");
}
void doWork(json& cfg){
std::string path = cfg["PATH"];
try
{
std::call_once(g_flag, doOnce, path);
}
catch (std::exception& ex)
{
fmt::print(ex.what());
}
}