简体   繁体   English

我应该在c ++中使用枚举或多个const作为非顺序常量吗?

[英]Should I use an enum or multiple const for non-sequential constants in c++?

I'm writing porting file-io set of functions from c into a c++ class. 我正在编写从c到c ++类的file-io函数集。 "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? 一个人对另一个人有什么好处吗?

eg 例如
OPTION 1 选项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 方案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... 我只是把项目_ <#> _...称为例子...

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). 最简单的方法可能是编写一个外部脚本来解析你的枚举定义并报告一个值是否重复(如果你愿意,你可以在构建过程中运行它)。

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) 你可以为enum分配一个int,它不对应任何值(太糟糕了)
  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 你不能迭代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: 在设计我的错误代码解决方案时,我因此选择了另一条道路:命名空间中的常量,在源文件中定义,它们针对第2点和第3点。虽然为了获得类型安全性,但常量不是int ,而是特定的Code类:

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: 但是我确实考虑过这个问题,通过将所需的构造函数设为私有并强制使用构建器,我们甚至可以获得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)
  /// ....
}

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 虽然只需扫描模式MAKE_NEW_ERROR_CODE就可以轻松诊断重复MAKE_NEW_ERROR_CODE

Have fun! 玩得开心!

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. 但是在C ++中,枚举类型已经是非迭代的。 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). 在许多情况下,您可能更喜欢严格控制常量值的类型(比如让它们取消签名),而且枚举无法帮助您(至少还有)。

要记住的一件事是你不能采用enum的地址:

const unsigned* my_arbitrary_item = &item_1_pos;

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. 如果它们纯粹是常量并且不需要运行时的东西(比如不能使用非枚举值的init枚举)那么它们应该只是const无符号整数。 Of course, the enum is less typing, but that's besides the point. 当然,枚举更少打字,但除此之外。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM