考虑导出用于初始化的独特接口的库,在使用库提供的任何其他内容之前必须由用户调用。在此步骤中,查询某些系统状态并将其存储在相应的变量中。这不能映射到常量,但也不应该因外部源的写入而容易发生变化,即系统状态应该可以在不同的转换单元中查询但不可写。
一个明显的例子是标记系统启动的时间戳。这不能是编译时常量,但也应该永远不可写。
这可以通过仅实现静态函数并使用私有静态成员的类来实现:
// system.h
#include <chrono>
using sys_time_point = std::chrono::system_clock::time_point;
class System
{
public:
System () = delete;
// possibly other deleted functions
static bool init () noexcept;
static bool ready () noexcept;
static const sys_time_point& initTime() noexcept;
private:
static bool initState_;
static sys_time_point initTime_;
};
// system.cpp
bool System::initState_ = false;
sys_time_point System::initTime_ = std::chrono::system_clock::now();
问题是,我认为后一种方法是一种不合适的设计选择,因为这些功能虽然可能相互依赖,但它们定义了或多或少的各种功能来查询系统状态,而不是修改或访问用户定义类型的私有状态。
我宁愿选择第二种方法。假设namespace System
将与先前类相同的功能分组
// System.h
#include <chrono>
namespace System
{
using sys_time_point = std::chrono::system_clock::time_point;
bool init () noexcept; // needs to be called
bool ready () noexcept; // can be used by other lib components and lib clients
const sys_time_point& initTime() noexcept; // returns system startup time point
// other stuff here ...
}
// System.cpp
namespace System
{
namespace
{
bool sysInitState = false;
sys_time_point sysInitTime = std::chrono::system_clock::now();
}
bool init() noexcept
{
// init code here ... set and return sysInitState accordingly
}
bool ready() noexcept
{
return sysInitState;
}
const sys_time_point& initTime() noexcept
{
return sysInitTime;
}
}
使用未命名的命名空间,我在其他翻译单元中禁止外部链接到变量。 AFAIK,除了使用namespace System
中的功能外,无法在其他翻译单元中访问它们。由于const引用或按值返回,写入也是不可能的 - 除了邪恶的程序员可能const_cast<>
const引用非const引用的情况。
我现在的问题是:
谢谢大家!
答案 0 :(得分:3)
隐藏命名空间中的私有信息是有效的,但我建议将它们放在一个结构中,并将该结构的一个副本存储在一个局部变量中。
// 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;
}
这种特殊技巧的原因是不同的.cpp文件中的全局变量没有初始化顺序。如果您的用户的.cpp文件之一在sysInitTime初始化之前调用示例中的init(),则init可能会使用错误值,或者更糟糕的是,sysInitTime初始化程序可能会更改其值,而您的库不会想。
静态局部变量保证在第一次调用函数时初始化一次。通过将数据存储在静态局部变量而不是全局变量中,我们确保您已经构建了可以使用的值。通过将它们与一个返回整个组的函数放在一个结构中,我们可以让开发人员更容易地证明它们都是构造和最新的(对于算法来说不是必需的,但它使得它更容易制作感觉代码)。
boost使用类似的模式,但它们不是将它们放在.cpp中的匿名命名空间中,而是将变量放在命名空间boost::detail
中。 Boost公开表示如果你开始在boost::detail
内部进行捣乱,可能会发生未定义的行为。他们这样做是因为他们的许多库只是标题,并且没有.cpp文件可以使用。我提出它是因为拥有detail
命名空间已成为对实现细节说“不接触”的可接受方式。