4

我正在编写将文件 io 函数集从 c 移植到 c++ 类中。“幻数”(未命名的常数)比比皆是。

这些函数读取一个文件头,该文件头具有许多特定条目,其位置当前由幻数表示。

几年前,一位资深程序员告诉我,使用“幻数”本质上是邪恶的,因此,我一直试图避免在我的端口中使用未命名的常量。所以我想创建某种存储条目的常量列表。

到目前为止,我已经提出了两个看起来相对安全的解决方案——使用命名空间封闭的常量集或命名空间封闭的枚举。

我可以安全地使用这两种解决方案吗?一个比另一个有什么优势吗?

例如
选项 1

namespace hdr_pos {
   const unsigned int item_1_pos=4;
   const unsigned int item_2_pos=8;
   const unsigned int item_3_pos=12;
   const unsigned int item_4_pos=24;
   const unsigned int item_5_pos=32;
};

选项 2

namespace hdr_pos {
   enum e {
      item_1_pos=4,
      item_2_pos=8,
      item_3_pos=12,
      item_4_pos=24,
      item_5_pos=32
   };
};

有没有办法防止重复,如果我由于将来更新文件头而改变位置,但忘记改变其中一个?

请保持事实性和非主观性。如果没有您知道的优势,请随时回答。

注意:当然,在我的实际实现中,我会使用更具描述性的名称;我只是将事物称为 item_<#>_... 以举例说明...

4

5 回答 5

2

我可以看到使用枚举的两个优点。首先,一些调试器可以将常量转换回枚举变量名(这在某些情况下可以使调试更容易)。此外,您可以声明一个枚举类型的变量,该变量只能保存来自该枚举的值。这可以为您提供一种额外形式的类型检查,而您只需使用常量就无法做到。

检查一个值是否重复可能取决于您的特定编译器。最简单的方法可能是编写一个外部脚本,该脚本将解析您的枚举定义并报告值是否重复(如果您愿意,可以将其作为构建过程的一部分运行)。

于 2010-09-17T19:24:40.187 回答
1

对于错误代码,我以前处理过这种情况。

我见过人们使用枚举作为错误代码,这会带来一些问题:

  1. 您可以将 int 分配给与任何值都不对应的枚举(太糟糕了)
  2. 该值本身在标头中声明,这意味着错误代码重新分配(发生这种情况...)破坏了代码兼容性,您在添加元素时还必须小心...
  3. 您必须在同一个标​​头中定义所有代码,即使通常某些代码自然地限制在应用程序的一小部分,因为枚举不能“扩展”
  4. 没有检查相同的代码没有分配两次
  5. 你不能遍历一个enum

在设计我的错误代码解决方案时,我因此选择了另一条路:命名空间中的常量,在源文件中定义,地址点 2 和 3。但为了获得类型安全,常量不是int,而是一个特定的Code类:

namespace error { class Code; }

然后我可以定义几个错误文件:

// error/common.hpp

namespace error
{
  extern Code const Unknown;
  extern Code const LostDatabaseConnection;
  extern Code const LostNASConnection;
}

// error/service1.hpp
// error/service2.hpp

虽然我没有解决任意转换问题(构造函数是显式的,但是是公开的),因为在我的情况下,我需要转发其他服务器返回的错误代码,而且我当然不想知道所有这些(即会太脆)

但是我确实考虑过,通过将所需的构造函数设为私有并强制使用构建器,我们甚至可以一举获得 4. 和 5.:

// error/code.hpp

namespace error
{
  class Code;

  template <size_t constant> Code const& Make(); // not defined here

  class Code: boost::totally_ordered<Code>
  {
  public:
    Code(): m(0) {} // Default Construction is useful, 0 is therefore invalid

    bool operator<(Code const& rhs) const { return m < rhs.m; }
    bool operator==(Code const& rhs) const { return m == rhs.m; }

  private:
    template <size_t> friend Code const& Make();
    explicit Code(size_t c): m(c) { assert(c && "Code - 0 means invalid"); }

    size_t m;
  };

  std::set<Code> const& Codes();
}


// error/privateheader.hpp (inaccessible to clients)

namespace error
{
  std::set<Code>& PrivateCodes() { static std::set<Code> Set; return Set; }

  std::set<Code> const& Codes() { return PrivateCodes(); }

  template <size_t constant>
  Code const& Make()
  {
    static std::pair< std::set<Code>::iterator, bool > r
      = PrivateCodes().insert(Code(constant));
    assert(r.second && "Make - same code redeclared");
    return *(r.first);
  }
}

//
// We use a macro trick to create a function whose name depends
// on the code therefore, if the same value is assigned twice, the
// linker should complain about two functions having the same name
// at the condition that both are located into the same namespace
//
#define MAKE_NEW_ERROR_CODE(name, value)         \
  Make<value>(); void _make_new_code_##value ();


// error/common.cpp

#include "error/common.hpp"

#include "privateheader.hpp"

namespace error
{
  Code const Unkown = MAKE_NEW_ERROR_CODE(1)
  /// ....
}

更多工作(对于框架),并且仅对相同分配检查进行链接时/运行时检查。虽然通过扫描模式很容易诊断重复MAKE_NEW_ERROR_CODE

玩得开心!

于 2010-09-17T21:31:48.780 回答
0

要记住的一件事是,您不能获取 a 的地址enum

const unsigned* my_arbitrary_item = &item_1_pos;

于 2010-09-17T19:44:41.893 回答
0

您的问题的标题表明您对使用枚举有疑问的主要原因是您的常量是non-iterative。但是在 C++ 中,枚举类型已经是非迭代的。您必须跳过很多圈才能创建迭代枚举类型。

我想说,如果你的常量本质上是相关的,那么 enum 是一个非常好的主意,不管常量是否是迭代的。枚举的主要缺点是完全缺乏类型控制。在许多情况下,您可能更喜欢对常量值的类型进行严格控制(例如让它们无符号),而这是 enum 无法帮助您的事情(至少目前如此)。

于 2010-09-17T19:22:35.223 回答
0

如果它们是纯粹的常量并且不需要运行时的东西(比如不能用非枚举值初始化枚举),那么它们应该只是 const 无符号整数。当然,枚举的类型更少,但这不是重点。

于 2010-09-17T19:53:51.013 回答