另外强调一下,为什么 Adrian McCarthy 的方法可能是自 C++11 以来在标准一致性、类型安全/显式清晰性和减少可能的歧义之间的折衷方面的最佳解决方案:
unsigned int flagsPreCpp11 = ~static_cast<unsigned int>(0);
auto flags = ~static_cast<unsigned int>(0); // C++11 initialization
predeclaredflags = ~static_cast<decltype(predeclaredflags)>(0); // C++11 assignment to already declared variable
我将在下面详细解释我的偏好。正如约翰内斯完全正确地提到的那样,这里激怒的根本原因是关于值与根据位表示语义以及我们正在谈论的确切类型(分配的值类型与可能的编译时间积分常量的类型)的问题。由于没有标准的内置机制来明确确保对于 OP 关于无符号整数值的具体用例将所有位设置为 1,因此很明显,这里不可能完全独立于值语义(std::bitset是一个常见的纯位层引用容器,但问题一般是关于无符号整数)。但我们或许可以减少这里的歧义。
“更好”的符合标准的方法的比较:
OP的方式:
unsigned int flags = -1;
专业人士:
- 是“成立的”并且很短
- 就“自然”位值表示的取模角度而言非常直观
- 例如,可以将目标无符号类型更改为无符号长,而无需任何进一步的调整
缺点:
- 至少初学者可能不确定是否符合标准(“我必须关心填充位吗?”)。
- 违反类型范围(以较重的方式:有符号与无符号)。
- 仅从代码中看不到任何位语义关联。
通过定义引用最大值:
unsigned int flags = UINT_MAX;
这规避了 -1 方法的有符号无符号转换问题,但引入了几个新问题:如果您想将目标类型更改为无符号长整数,则必须再次查看这里两次。在这里,必须确定这样一个事实,即最大值导致标准将所有位设置为 1(以及再次填充位问题)。位语义在这里也不是很明显,直接从代码中再看一遍。
更明确地引用最大值:
auto flags = std::numeric_limits<unsigned int>::max();
在我看来,这是更好的最大值方法,因为它是免费的宏/定义,并且对所涉及的类型是明确的。但是关于方法类型本身的所有其他问题仍然存在。
Adrian 的方法(以及为什么我认为它是 C++11 之前和之后的首选方法):
unsigned int flagsPreCpp11 = ~static_cast<unsigned int>(0);
auto flagsCpp11 = ~static_cast<unsigned int>(0);
专业人士:
- 仅使用最简单的整数编译时间常数:0。因此不必担心进一步的位表示或(隐式)强制转换。从直观的角度来看,我认为我们都可以同意这样一个事实,即 0 的位表示通常比最大值更清晰,不仅仅是无符号积分。
- 不涉及类型歧义,无需进一步查找有疑问。
- 此处通过补码 ~ 涉及显式位语义。所以从代码中可以很清楚地看出意图是什么。而且,补码适用于哪种类型和类型范围也非常明确。
缺点:
例如,如果分配给一个成员,那么您很可能会与 C++11 之前的类型不匹配:
类中的声明:
unsigned long m_flags;
在构造函数中初始化:
m_flags(~static_cast<unsigned int>(0))
但是从 C++11 开始,使用 decltype + auto 可以很好地防止大多数可能出现的问题。并且这些类型不匹配的场景中的一些(例如在接口边界上)对于 -1 方法也是可能的。
用于预声明变量的强大的最终 C++11 方法:
m_flags(~static_cast<decltype(m_flags)>(0)) // member initialization case
因此,在全面了解此处所有方法的优缺点的权重后,我推荐此方法作为首选方法,最迟自 C++11 以来。
更新:感谢 Andrew Henle 的提示,我删除了关于其可读性的声明,因为这可能是一个过于主观的声明。但我仍然认为,它的可读性至少不比大多数最大值方法或通过编译时积分/文字明确提供最大值的方法差,因为 static_cast-usage 也“建立”并内置于定义/宏,甚至是标准库。