6

My main platform (WinAPI) still heavily uses bits for control styles etc. (example).

When introducing custom controls, I'm permanently wondering whether to follow that style or rather use individual bool's. Let's pit them against each other:

enum EMyCtrlStyles
{
   mcsUseFileIcon = 1,
   mcsTruncateFileName = 2,
   mcsUseShellContextMenu = 4,
};

void SetStyle(DWORD mcsStyle);
void ModifyStyle(DWORD mcsRemove, DWORD mcsAdd);
DWORD GetStyle() const;

...
ctrl.SetStyle(mcsUseFileIcon | mcsUseShellContextMenu);

vs.

CMyCtrl & SetUseFileIcon(bool enable = true);
bool GetUseFileIcon() const;

CMyCtrl & SetTruncteFileName(bool enable = true);
bool GetTruncteFileName() const;

CMyCtrl & SetUseShellContextMenu(bool enable = true);
bool GetUseShellContextMenu() const;

ctrl.SetUseFileIcon().SetUseShellContextMenu();

As I see it,

Pro Style Bits

  • Consistent with platform
  • less library code (without gaining complexity), less places to modify for adding a new style
  • less caller code (without losing notable readability)
  • easier to use in some scenarios (e.g. remembering / transferring settings)
  • Binary API remains stable if new style bits are introduced

Now, the first and the last are minor in most cases.

Pro Individual booleans

  • Intellisense and refactoring tools reduce the "less typing" effort
  • Single Purpose Entities
  • more literate code (as in "flows more like a sentence")
  • No change of paradim for non-bool properties

These sound more modern, but also "soft" advantages. I must admit the "platform consistency" is much more enticing than I could justify, the less code without losing much quality is a nice bonus.

1. What do you prefer? Subjectively, for writing the library, or for writing client code?
2. Any (semi-) objective statements, studies, etc.?

1
  • pro style bits each boolean is generally allocated as (at least) a byte, this means less copying Sep 25, 2012 at 7:44

3 Answers 3

11

Actually, I don't believe that "consistency with platform" is a "minor" point in most cases. Quite to the contrary, you should design a library like others are likely to expect to use it. If the primary target audience for, or platform that is targetted by, your library uses bitfields, then you are probably better off using bitfields.

That said, bitfields do have their fair share of disadvantages. They debuted when memory constraints were a very real concern; if you're trying to squeeze something useful into a few dozen kilobytes of RAM or even less, then every byte counts. Consider that the Windows API goes back to the early 1980s, with Windows 1.0 released in 1985 and requiring a whopping 256 KB of RAM and either two floppy disk drives or a single hard drive. This is much less of a concern today now that pretty much all and sundry have several gigabytes of RAM available for server or desktop applications, as well as essentially unlimited, comparatively extremely fast swap space (I don't think Windows 1.0 ever did swapping at all, although I'm pretty sure it did memory page purging), plus JIT compilers, optimizers etc.

Bitfields are opaque; it's much easier to say if (frob.isFrobnicated() && frob.isGlobbified()) than if (frob.getState() & (FROB_FROBNICATED | FROB_GLOBBIFIED) == (FROB_FROBNICATED | FROB_GLOBBIFIED)), let alone when you are looking at the object in a debugger and see that frob.state == 0x27a and trying to figure out what that value means.

One big upside of using separate state variables and accessor/mutator functions is that it is much easier to enforce invariants between related states. For example, if state A has no special requirements, but state B requires that one of but not both of states C and D are set, this is trivial to enforce at compile time if you are using separate state mutator functions, but not quite so obvious when using bitfields.

I work primarily with .NET and Java, both of which frown at bitfields for exposed state in library routines. (There are exceptions, but they are rare.) C is almost certainly different even these days, and I imagine C++ kind of bridges the gap.

Bit fields have one huge advantage: they are atomic. If you access frob.state, you can examine the value safe in the knowledge that it cannot change between comparisons within the same statement (like for example in the if examples above). In a multithreaded application, if you are unlucky, the state of the frob might change between the two calls to isFrobnicated() and isGlobbified(). However, I would think that if this is a major source of bugs, then you have other issues that you need to deal with (like thread-common global state being modified while being used).

Bottom line: you certainly can use bit fields internally if you feel comfortable with them, but for exposed state, I would go with separate values unless there are specific reasons (such as platform usage expectations) for doing otherwise, and you are willing to live with the downsides of that choice. And even then, you may want to consider doing it as an alternative rather than as the only available approach.

4
  • I wish languages would better support bitmask enumerations (e.g. with has, hasAny, and hasAll operations, and allow EnumType.(Value1 | Value2) rather than (EnumType.Value1 | EnumType.Value2)), since they have considerable semantic advantages over individual Booleans. Pascal included "set" types which were often limited to values 0..255 because Pascal 0..7 wouldn't be very useful, and going much beyond that would require the same amount of complexity for any set size; in today's 64-bit world, having set types for 32 and 64 items, with suitable syntax, would be very helpful.
    – supercat
    Jul 14, 2014 at 17:22
  • @supercat Recall that in many languages, enumeration names can correspond to any value (not necessarily even listed in numerical order in the source code), and the default often is not bitshifted-single-ones. So you'd need some way to detect that usage and act appropriately in each distinct case. A lot of complexity for what I would argue is marginal gain; (int)enumVar & (EnumType.Value1 | EnumType.Value2) != 0 (to be explicit) is only marginally more typing than even enumVar.hasAll(EnumType.{Value1,Value2}) and IMO easier to read if only you know the binary operators.
    – user
    Jul 15, 2014 at 7:22
  • I wouldn't mind the latter syntax either, though Boolean operators can be useful for things like BoundsSpecified.(Any & ~Width). My main point is that it's often useful to group many flags together, and a method which takes an integer that combines multiple flags can be easier to work with and less brittle than one which takes a bunch of separate flags. Consider also it's possible to add options to a flags value received from an interface without breaking existing implementations. This can be very useful when aggregating or composing interface implementations in wrapper classes.
    – supercat
    Jul 15, 2014 at 15:24
  • Although some people dislike methods of a form like Object DoSomething(CommandSelector command, Object parameters), such methods can be a useful part of an interface if an acceptable implementation for any unknown commands would be return InterfaceHelper.DoSomething(it, command, parameters)). Using such a design, it's possible to add abilities to an interface without breaking existing implementations or wrapper classes; when using such a design, flags enumerations can provide a nice means for an aggregate to indicate what actions are supported by any or all constituents.
    – supercat
    Jul 15, 2014 at 15:40
1

I prefer using enum bitflags in both library and client code for a collection of boolean options that can be toggled independently. It's an idiom I'm familiar with, and allows me to save the configuration in a single int and pass it around without having to define a new struct or class to contain all the flags. It also cuts down on typing without really sacrificing clarity.

Objectively, a struct containing bools would be more type-safe in C++, as discussed in this SO question. Irrelevant if you're using plain C.

1
  • Thanks - The C++ 0x solution looks darn nice, though i have to delve deeper...
    – peterchen
    Sep 25, 2012 at 8:03
1

The booleans have almost all the advantages. All the bit advantages you mentioned except "Consistent with platform" can be completely overcome by carefully packaging booleans into classes. Booleans make for dramatically cleaner code and better design.

However, bits do have one very big advantage. They are faster and use less memory. If you have 10 million objects, each with a set of 60 boolean values, your program will run faster and use a lot less memory if you use the bits. (This is why the platform still uses bits.) You have to decide at the start of a project how much performance matters, because it will be very hard to switch your booleans to bits later.

If I have a couple of booleans in a class that may end up with a couple hundred instances, I'll stick with booleans. If it's the 10 million and 60, and performance matters, I go with the bits. If it's in the middle, I end up thinking for a while.

2
  • 1
    The argument that bitfields use less memory is only relevant if you consider internal and external state to be one and the same and you are operating under memory constraints (which, as you correctly point out, you might even in a large-memory environment). There is nothing in general saying that you can't represent a state value internally as a bitfield while exposing it as a set of booleans and/or enums by way of a set of methods and functions, or properties (a.k.a. syntactic sugar for methods and functions).
    – user
    Sep 25, 2012 at 14:47
  • @MichaelKjörling: True. While bitfields can get a bit ugly, it is usually pretty easy to hide them. If 60 bits can be stuffed neatly into one elegant class, there's almost no reason to prefer booleans (for actual storage, not methods/properties!), even when the performance benefits are minor. Sep 25, 2012 at 15:26

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.