我正在编写从c到c ++类的file-io函数集。 “魔术数字”(未命名的常数)比比皆是。
这些函数读取一个文件头,该头文件具有许多特定条目,其位置当前用幻数表示。
几年前,我被一位资深程序员教过,使用“魔法数字”本来就是邪恶的,因此,我已经试图避免在我的端口使用未命名的常量。所以我想创建一些存储条目的常量列表。
到目前为止,我已经提出了两个似乎相对安全的解决方案 - 使用命名空间封闭的常量集或命名空间封闭的枚举。
我可以安全使用任何一种解决方案吗?一个人对另一个人有什么好处吗?
e.g。
选项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_<#> _... ...
答案 0 :(得分:2)
我可以看到使用枚举的两个好处。首先,一些调试器可以将常量转换回枚举变量名(在某些情况下可以使调试更容易)。此外,您可以声明枚举类型的变量,该变量只能保存该枚举中的值。这可以为您提供一种额外的类型检查形式,您不会只使用常量。
检查值是否重复可能取决于您的特定编译器。最简单的方法可能是编写一个外部脚本来解析你的枚举定义并报告一个值是否重复(如果你愿意,你可以将其作为构建过程的一部分运行)。
答案 1 :(得分:1)
我之前已经处理过这种情况,错误代码。
我见过人们使用枚举来获取错误代码,这会带来一些问题:
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
玩得开心!
答案 2 :(得分:0)
你的问题的标题表明,你对使用枚举有疑问的主要原因是你的常量非迭代。但是在C ++中,枚举类型已经是非迭代的。你必须通过相当多的箍来制作迭代枚举类型。
我会说,如果你的常量本质上是相关的,那么枚举是一个非常好的主意,无论常数是否是迭代的。然而,枚举的主要缺点是完全缺乏类型控制。在许多情况下,您可能更喜欢严格控制常量值的类型(比如让它们无符号),而且枚举无法帮助您(至少还有)。
答案 3 :(得分:0)
要记住的一件事是,您无法获取enum
的地址:
const unsigned* my_arbitrary_item = &item_1_pos;
答案 4 :(得分:0)
如果它们纯粹是常量并且不需要运行时的东西(比如不能使用非枚举值的init枚举)那么它们应该只是const无符号整数。当然,枚举更少打字,但除此之外。