1

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

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

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

// 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 refs 或按值返回,也无法写入 - 除了邪恶的程序员可能const_cast<>将 const refs 引用为 non-const refs 的情况。

我现在的问题是:

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

感谢大家!

4

1 回答 1

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命名空间已成为一种公认的方式,即对实现细节说“不接触”。

于 2013-09-13T16:04:34.047 回答