在不使用类的情况下隐藏可查询的程序状态?

时间:2013-09-12 23:57:02

标签: c++ class c++11 namespaces linkage

考虑导出用于初始化的独特接口的库,在使用库提供的任何其他内容之前必须由用户调用。在此步骤中,查询某些系统状态并将其存储在相应的变量中。这不能映射到常量,但也不应该因外部源的写入而容易发生变化,即系统状态应该可以在不同的转换单元中查询但不可写。

一个明显的例子是标记系统启动的时间戳。这不能是编译时常量,但也应该永远不可写。

这可以通过仅实现静态函数并使用私有静态成员的类来实现:

// 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引用的情况。

我现在的问题是:

  • 上面的第二个解决方案是否正确?
  • 是上述第二种解决方案可行吗?
  • 还有其他可能更安全或更容易实施的解决方案吗?

谢谢大家!

1 个答案:

答案 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命名空间已成为对实现细节说“不接触”的可接受方式。