我应该对 c++ 中的非顺序常量使用枚举还是多个 const?

发布于 2024-09-19 23:07:16 字数 939 浏览 7 评论 0原文

我正在编写将 file-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_<#>_...作为示例...

I'm writing porting file-io set of functions from c into a c++ class. "Magic numbers" (unnamed constants) abound.

The functions read a file header which has a number of specific entries whose locations are currently denoted by magic numbers.

I was taught by a veteran programmer a couple years back that using "magic numbers" is inherently evil, and thus, I have since tried to avoid using unnamed constants in my port. So I want to create some sort of list of constants of where the entries are stored.

So far I've come up with two solutions that seem relatively safe -- use a namespace enclosed set of constants or a namespace enclosed enum.

Can I use either solution safely? Would there be any advantages to one over the other?

e.g.
OPTION 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;
};

OPTION 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
   };
};

Is there anyway to prevent duplicates, to catch if I change the positions due to a future update to the file header, but forget to change one of them?

Please keep this factual and non-subjective. If there is no advantage you know of, feel free to answer that.

Note: I would use more descriptive names, of course, in my actual implementation; I just called things item_<#>_... for examples sake...

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(5

想挽留 2024-09-26 23:08:38

如果它们纯粹是常量并且不需要运行时的东西(比如不能用非枚举值初始化枚举)那么它们应该只是 const unsigned ints。当然,枚举的输入量较少,但这不是重点。

If they're purely constants and require no run-time stuff (like can't init enum with non-enum value) then they should just be const unsigned ints. Of course, the enum is less typing, but that's besides the point.

彩虹直至黑白 2024-09-26 23:08:30

需要记住的一件事是,您不能获取枚举的地址:

const unsigned* my_ Arbitary_item = &item_1_pos;

One thing to keep in mind is that you can't take the address of an enum:

const unsigned* my_arbitrary_item = &item_1_pos;

清欢 2024-09-26 23:08:20

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

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

The title of your question suggests that the main reason you have doubts about using a enum is that your constants are non-iterative. But in C++ enum types are non-iterative already. You have to jump through quite a few hoops to make an iterative enum type.

I'd say that if your constants are related by nature, then enum is a pretty good idea, regardless of whether the constants are iterative or not. The main disadvantage of enums though is total lack of type control. In many cases you might prefer to have strict control over the types of your constant values (like have them unsigned) and that's something enum can't help you with (at least yet).

陈甜 2024-09-26 23:08:08

我以前也遇到过这种情况,有错误代码。

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

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

在设计我的错误代码解决方案时,我因此选择了另一条路:命名空间中的常量,在源文件中定义,地址为 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

虽然我没有解决任意强制转换问题(构造函数是显式的,但是公共的),因为在我的例子中,我需要转发其他服务器返回的错误代码,而我当然没有想要了解所有这些(这太脆弱了)

但是我确实考虑过,通过将所需的构造函数设为私有并强制使用构建器,我们甚至可以在swoop:

// 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 即可轻松诊断重复项,但

祝您玩得开心!

I've dealt with this situation before, for error codes.

I have seen people using enums for error codes, and this pose some issues:

  1. you can assign an int to the enum that doesn't not correspond to any value (too bad)
  2. the value itself is declared in a header, meaning that error code reassignment (this happens...) breaks code compatibility, you also have to take care when adding elements...
  3. you have to define all codes in the same header, even if often times some code are naturally restricted to a small portion of the application, because enums cannot be "extended"
  4. there is no check that a same code is not assigned twice
  5. you cannot iterate over the various fields of an enum

When designing my error codes solution, I thus chose another road: constants in a namespace, defined in source files, which address points 2 and 3. To gain in type safety though, the constants are not int, but a specific Code class:

namespace error { class Code; }

Then I can define several error files:

// error/common.hpp

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

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

I didn't solved the arbitrary cast issue though (constructor is explicit, but public), because in my case I was required to forward error codes returned by other servers, and I certainly didn't want to have to know them all (that would have been too brittle)

However I did thought about it, by making the required constructor private and enforcing the use of a builder, we're even going to get 4. and 5. in a swoop:

// 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)
  /// ....
}

A tad more work (for the framework), and only link-time/run-time check of the same assignment check. Though it's easy to diagnose duplicates simply by scanning for the pattern MAKE_NEW_ERROR_CODE

Have fun!

甜柠檬 2024-09-26 23:07:57

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

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

I can see two advantages to using an enum. First, some debuggers can translate constants back into enum variable names (which can make debugging easier in some cases). Also, you can declare a variable of an enumerated type which can only hold a value from that enumeration. This can give you an additional form of type checking that you wouldn't have simply by using constants.

Checking to see if a value is duplicated might depend on your particular compiler. The easiest way to do so would probably be to write an external script that will parse your enum definition and report whether or not a value is duplicated (you can run this as part of your build process if you like).

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文