Obscuring the private information in a namespace works, but I recommend putting them in a struct and storing one copy of that struct in a local variable.
// System.cpp
namespace {
struct SysInit
{
bool state;
sys_time_point time;
SysInit()
: state(false)
, time (std::chrono::system_clock::now())
{ }
static SysInit& instance()
{
static SysInit rval;
return rval;
}
};
}
void init() noexcept
{
SysInit::instance().state = true;
}
bool ready() noexcept
{
return SysInit::instance().state;
}
const sys_time_point& initTime() noexcept
{
return SysInit::instance().time;
}
The reason for this peculiar trick is there is no order of initialization for globals in different .cpp files. If one of your user's .cpp files calls init() in your example before sysInitTime gets initialized, init may use bad values, or worse, the sysInitTime initializer might change its value, which your library doesn't want.
Static local variables are guaranteed to be initialized once, the first time a function gets called. By storing the data in a static local variable rather than a global, we ensure you have constructed values ready to work with. By putting them in a struct together with one function that returns the whole group, we make it easier on the developer to prove that they are all indeed constructed and up to date (not essential for the algorithm, but it makes it much easier to make sense of the code).
A similar pattern is used by boost, but instead of putting them in an anonymous namespace in a .cpp, they put the variables inside a namespace boost::detail
. Boost openly states that if you start mucking around inside boost::detail
undefined behavior can occur. They do this because many of their libraries are header only, and don't have a .cpp file to work with. I bring it up because having a detail
namespace has become an accepted way of saying "no-touch" to implementation details.