【问题标题】:Why Does C++ Support Hex Assignment, But Lack Binary Assignment? How best to store flags?为什么 C++ 支持十六进制赋值,但缺少二进制赋值?如何最好地存储标志?
【发布时间】:2010-09-30 19:58:30
【问题描述】:

我有一组用于从 C 移植到 C++ 的程序中的位标志。

开始...

我程序中的标志之前定义为:

/* Define feature flags for this DCD file */
#define DCD_IS_CHARMM       0x01
#define DCD_HAS_4DIMS       0x02
#define DCD_HAS_EXTRA_BLOCK 0x04

...现在我收集到常量(相对于类常量等)的#defines 通常被认为是错误的形式。

这引发了关于如何最好地在 c++ 中存储位标志以及为什么 c++ 不支持将二进制文本分配给 int 的问题,例如它允许以这种方式分配十六进制数字(通过“0x”)。这些问题在本文末尾进行了总结。

我可以看到一个简单的解决方案是简单地创建单独的常量:

namespace DCD {
   const unsigned int IS_CHARMM = 1;
   const unsigned int HAS_4DIMS = 2;
   const unsigned int HAS_EXTRA_BLOCK = 4;
};

我们称这个想法为 1。

我的另一个想法是使用整数枚举:

namespace DCD {
   enum e_Feature_Flags {
      IS_CHARMM = 1,
      HAS_4DIMS = 2,
      HAS_EXTRA_BLOCK = 8
   };
};

但让我感到困扰的一件事是,当涉及到更高的值时,它的直观性似乎……即

namespace DCD {
   enum e_Feature_Flags {
      IS_CHARMM = 1,
      HAS_4DIMS = 2,
      HAS_EXTRA_BLOCK = 8,
      NEW_FLAG = 16,
      NEW_FLAG_2 = 32,
      NEW_FLAG_3 = 64,
      NEW_FLAG_4 = 128
   };
};

我们将这种方法称为选项 2。

我正在考虑使用 Tom Torf 的宏解决方案:

#define B8(x) ((int) B8_(0x##x))

#define B8_(x) \
( ((x) & 0xF0000000) >( 28 - 7 ) \
| ((x) & 0x0F000000) >( 24 - 6 ) \
| ((x) & 0x00F00000) >( 20 - 5 ) \
| ((x) & 0x000F0000) >( 16 - 4 ) \
| ((x) & 0x0000F000) >( 12 - 3 ) \
| ((x) & 0x00000F00) >( 8 - 2 ) \
| ((x) & 0x000000F0) >( 4 - 1 ) \
| ((x) & 0x0000000F) >( 0 - 0 ) )

转换为内联函数,例如

#include <iostream>
#include <string>
....

/* TAKEN FROM THE C++ LITE FAQ [39.2]... */
class BadConversion : public std::runtime_error {
 public:
   BadConversion(std::string const& s)
     : std::runtime_error(s)
     { }
};

inline double convertToUI(std::string const& s)
{
   std::istringstream i(s);
   unsigned int x;
   if (!(i >> x))
     throw BadConversion("convertToUI(\"" + s + "\")");
   return x;
} 
/** END CODE **/

inline unsigned int B8(std::string x) {
   unsigned int my_val = convertToUI(x.insert(0,"0x").c_str());
   return ((my_val) & 0xF0000000) >( 28 - 7 ) |
            ((my_val) & 0x0F000000) >( 24 - 6 ) | 
            ((my_val) & 0x00F00000) >( 20 - 5 ) |
            ((my_val) & 0x000F0000) >( 16 - 4 ) |
            ((my_val) & 0x0000F000) >( 12 - 3 ) |
            ((my_val) & 0x00000F00) >( 8 - 2 ) |
            ((my_val) & 0x000000F0) >( 4 - 1 ) |
            ((my_val) & 0x0000000F) >( 0 - 0 );
}

namespace DCD {
   enum e_Feature_Flags {
      IS_CHARMM       = B8("00000001"),
      HAS_4DIMS       = B8("00000010"),
      HAS_EXTRA_BLOCK = B8("00000100"),
      NEW_FLAG        = B8("00001000"),
      NEW_FLAG_2      = B8("00010000"),
      NEW_FLAG_3      = B8("00100000"),
      NEW_FLAG_4      = B8("01000000")
   };
};

这很疯狂吗?还是看起来更直观?我们称这个选择为 3。

所以回顾一下,我的首要问题是:

1. 为什么 c++ 不支持“0b”值标志,类似于“0x”?
2. 最好定义哪种样式标志...
i. 命名空间包装的常量。
ii. 命名空间包装的直接分配的无符号整数枚举。
iii. 使用可读二进制字符串分配的无符号整数的命名空间包装枚举。

提前致谢!并且请不要因为主观而关闭此线程,因为我真的很想就什么是最好的样式以及为什么 c++ 缺乏内置的二进制赋值功能获得帮助。


编辑 1

一些附加信息。我将从文件中读取 32 位位域,然后使用这些标志对其进行测试。因此,在发布建议时请记住这一点。

【问题讨论】:

    标签: c++ binary inline variable-assignment flags


    【解决方案1】:

    在 C++14 之前,多年来一直在讨论二进制文字,但据我所知,没有人写过严肃的提案将其纳入标准,因此它从未真正通过谈论它的阶段。

    对于 C++ 14,终于有人写了一个提案,委员会接受了它,所以如果你可以使用当前的编译器,这个问题的基本前提是错误的——你可以使用二进制文字,其形式为 @ 987654321@.

    在 C++11 中,它们不是直接添加二进制文字,而是添加了一种更通用的机制来允许通用的用户定义文字,您可以使用它来完全支持二进制、base 64 或其他类型的东西。基本思想是您指定一个数字(或字符串)文字后跟一个后缀,您可以定义一个接收该文字的函数,并将其转换为您喜欢的任何形式(并且您可以将其状态保持为“常量"也...)

    至于使用哪个:如果可以的话,内置于 C++14 或更高版本的二进制文字是显而易见的选择。如果您不能使用它们,我通常更喜欢选项 2 的变体:带有十六进制初始化器的枚举:

    namespace DCD {
       enum e_Feature_Flags {
          IS_CHARMM = 0x1,
          HAS_4DIMS = 0x2,
          HAS_EXTRA_BLOCK = 0x8,
          NEW_FLAG = 0x10,
          NEW_FLAG_2 = 0x20,
          NEW_FLAG_3 = 0x40,
          NEW_FLAG_4 = 0x80
       };
    };
    

    另一种可能性是:

    #define bit(n) (1<<(n))
    
    enum e_feature_flags = { 
        IS_CHARM = bit(0), 
        HAS_4DIMS = bit(1),
        HAS_EXTRA_BLOCK = bit(3),
        NEW_FLAG = bit(4),
        NEW_FLAG_2 = bit(5),
        NEW_FLAG_3 = bit(6),
        NEW_FLAG_4 = bit(7)
    };
    

    【讨论】:

    • @Jason:不;枚举器初始化器必须是常量表达式;你不能在常量表达式中调用函数,所以它必须是一个宏。
    • hrmm...我想现在是使用宏的好时机?
    • @Jason:如果你是自虐狂,你可以使用模板。完成后,您可以随时#undef 宏。就我个人而言,我只是写出面具。十六进制与您在调试器中看到的相匹配,无论如何您不会经常更改它。要么它对应于一种文件格式,在这种情况下你基本上永远不会改变它,或者它没有,在这种情况下你最好使用位域而不用任何掩码。
    • @Jason:对@James McNellis 已经指出的内容的一个小补充:在 C++0x 中,您应该能够使用 constexpr 函数。
    • Binary literals are one of those things that's been discussed off and on over the years, but as far as I know, nobody's ever written up a serious proposal to get it into the standard, so it's never really gotten past the stage of talking about it. 我相信你知道,对于 C++14,他们做到了! 0b11110000et al 的形式。所以这可能值得更新。
    【解决方案2】:

    使用选项二,您可以使用左移,这可能不太“不直观”:

    namespace DCD { 
       enum e_Feature_Flags { 
          IS_CHARMM =        1, 
          HAS_4DIMS =       (1 << 1), 
          HAS_EXTRA_BLOCK = (1 << 2), 
          NEW_FLAG =        (1 << 3), 
          NEW_FLAG_2 =      (1 << 4), 
          NEW_FLAG_3 =      (1 << 5), 
          NEW_FLAG_4 =      (1 << 6) 
       }; 
    };
    

    【讨论】:

    • Jerry 使用bit(n) 的方法更直观地阅读代码,但您的方法不使用宏。啊,我只能选择一个答案……你们俩都提出了很好的建议。任何关于为什么从来没有标准化“0b”或某种类似形式的作业的提案的见解?
    【解决方案3】:

    就像一个注释一样,Boost(像往常一样)提供了这个想法的implementation

    【讨论】:

    • 虽然这确实很诱人,但 Boost 不是官方的 c++,我觉得将它拖到我的代码项目中会引入可能的依赖/平台限制,我不想作为时间处理 -自学 c++ 的有限程序员。此外,我的同事对编程世界的了解甚至比我还多(我是一名 CE,所以至少有 Java 和 prog.basic w/visual basic 的课程)所以我不太相信他们能够维持我的提升驱动的代码....
    • 不要害怕图书馆。尤其是 Boost,它是非常跨平台的。
    【解决方案4】:

    为什么不使用bitfield struct

    struct preferences {
            unsigned int likes_ice_cream : 1;
            unsigned int plays_golf : 1;
            unsigned int watches_tv : 1;
            unsigned int reads_stackoverflow : 1;
        }; 
    
    struct preferences fred;
    
    fred.likes_ice_cream = 1;
    fred.plays_golf = 0;
    fred.watches_tv = 0;
    fred.reads_stackoverflow = 1;
    
    if (fred.likes_ice_cream == 1)
        /* ... */
    

    【讨论】:

    • 我正在从一个文件中读取一个整数值,并将使用&amp; 对照标志检查它。我想您的解决方案可以适应这种情况,但似乎不太直观。
    • 也可以说您正在从文件中读取 32 位位域。
    【解决方案5】:

    GCC 有一个扩展使其能够进行二进制赋值:

    int n = 0b01010101;
    

    编辑:从 C++14 开始,这现在是该语言的官方部分。

    【讨论】:

    • 我建议编辑注意,从 C++14 开始,这是官方语法,而不是扩展。
    【解决方案6】:

    这个用例的十六进制有什么问题?

    enum Flags {
        FLAG_A = 0x00000001,
        FLAG_B = 0x00000002,
        FLAG_C = 0x00000004,
        FLAG_D = 0x00000008,
        FLAG_E = 0x00000010,
        // ...
    };
    

    【讨论】:

      【解决方案7】:

      我想归根结底是没有必要。

      如果你只是想使用二进制作为标志,下面的方法是我通常的做法。在原始定义之后,您永远不必担心像您提到的那样查看“更混乱”的 2 的更大倍数

      int FLAG_1 = 1
      int FLAG_2 = 2
      int FLAG_3 = 4
      ...
      int FLAG_N = 256
      

      您可以使用

      轻松检查它们
      if(someVariable & FLAG_3 == FLAG_3) {
         // the flag is set
      }
      

      顺便说一句,根据您的编译器(我使用的是 GNU GCC 编译器),它可能支持“0b”

      注释已编辑以回答问题。

      【讨论】:

      • 但是,这是一个特定于编译器的、不可移植的扩展。它不是 C++ 的一部分。
      • 使用 g++ 我得到错误invalid suffix "b0101" on integer constant ...所以显然在赋值中使用“0b”不是交叉编译器安全的,也不是官方支持/可接受的解决方案。
      • 有趣,在我认为它在标准中并且编译器选择忽略它之前。不知道情况正好相反。
      • 哦,既然你也在使用 g++,我的版本是 g++ (GCC) 4.1.2 20080704 (Red Hat 4.1.2-44) ,我只是使用 g++ main.cc 进行编译
      • gcc 决定在某个时候支持它,尽管我的 v4.2 也不喜欢它。 gcc.gnu.org/onlinedocs/gcc/…
      猜你喜欢
      • 1970-01-01
      • 2010-12-15
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-10-20
      • 2016-06-05
      相关资源
      最近更新 更多