Monday, September 22, 2008

Bitflags Part 1: Using properties to access bitflags

You may have a need to record a series of yes/no, true/false type properties in a class or record. Each one of these may be thought of as a flag, or collectively as a set.

An approach often taken to implement sets of flags is as follows (Note: while I have used a class here, a record may be used with essentially the same approach):

type
TMyClass = class(TObject)
Flag1, Flag2, Flag2 : Boolean;
end;


or, if you're feeling like being more rigorous:

type
TMyClass = class(TObject)


private
FFlag1, FFlag2, FFlag2 : Boolean;


public
property Flag1 : Boolean read FFlag1 write FFlag1;
property Flag2 : Boolean read FFlag1 write FFlag2;

property Flag3 : Boolean read FFlag1 write FFlag3;
end;


Older hands look at this and say: You're using a byte for each flag, why not collapse the flags into a single flags byte and test the appropriate bit. After some jiggling, the code looks like this:

const
kFlag1 = $1;
kFlag2 = $2;
kFlag3 = $4;

type
TMyClass = class(TObject)
private

FFlags : Byte;
public
property Flags : Byte read FFlags write Flags;

end;

Yes, that simplified the declaration of the class all right, but it just complicated the code that uses instances of this class.

Before it was possible to write:

var
MyInstance : TMyClass;
...
if MyInstance.Flag1 then
...


Now you need to write it like this:

var
MyInstance : TMyClass;
...
if (MyInstance.Flags and kFlag1) <> 0 then
...


Which is nowhere near as clear, and plasters implementation details of TMyClass throughout the code that uses it.

We could encapsulate the bit flag testing in the class by extending it like this:

type
TMyClass = class(TObject)


private
FFlags : Byte;


procedure SetFlag1(Value : Boolean);
function GetFlag1 : Boolean;

procedure SetFlag2(Value : Boolean);
function GetFlag2 : Boolean;


procedure SetFlag3(Value : Boolean);
function GetFlag3 : Boolean;


public
property Flag1 : Boolean read GetFlag1 write SetFlag1;
property Flag2 : Boolean read GetFlag2 write SetFlag2;

property Flag3 : Boolean read GetFlag3 write SetFlag3;
end;

procedure TMyClass.SetFlag1(Value : Boolean);
begin
if Value then
FFlags := FFlags OR kFlag1
else
FFlags := FFlags AND NOT kFlag1;
end;

function TMyClass.GetFlag1 : Boolean;
begin
Result := (FFlags and kFlag1) <> 0;
end;


Note: I have omitted the Flag1 and Flag2 setter & getter methods for brevity.

Now the implementation detail of the flag is again encapsulated in the class and hidden from the caller which may treat the flag as if it were a boolean property. However, this is a bit clumsy as I have to write new setter and getter methods for each flag that differs only in the use of a particular kFlag(x) constant I have previously defined. Wouldn't it be nice to be able to declare a new flag as an additional boolean property, but not have to define additional constancts or setter/getter methods?

Well, you can! Enter the index modifier for property delcarations. This has been around for many Delphi versions.

Now I can write the class above in a way that generically deals with the flags:

type
TMyClass = class(TObject)
private

FFlags : Byte;

procedure SetFlag(Value : Boolean);
function GetFlag : Boolean;
public
property Flag1 : Boolean Index 0 read GetFlag write SetFlag;

property Flag2 : Boolean Index 1 read GetFlag write SetFlag;
property Flag3 : Boolean Index 2 read GetFlag write SetFlag;
end;

procedure TMyClass.SetFlag(Value : Boolean;

Index : integer);
begin
if Value then
FFlags := FFlags OR (1 SHL Index)
else
FFlags := FFlags AND NOT (1 SHL Index);
end;


function TMyClass.GetFlag(Index : integer) : Boolean;
begin
Result := (FFlags and (1 SHL index)) <> 0;
end;


See the difference? The use of the index keyword in the property delcaration provides an additional parameter to the setter and getter methods. Now, a single pair of setter/getter methods can support all the flags in class. To add a fourth flag all I need to do is to add an additional property declaring it with a different index number and I'm done!

No constants need to be defined, the implementation is completely hidden from the caller, and the caller may treat the flags as if they were individual boolean values.

So, why would I do this? It introduces overhead into the querying of the value of a flag and might seem to complicate a class (or record) unnecessarily. In many cases you would not worry about it, especially if the instances of the classes or records in question were singletons, or not numerous. However, in some cases there may be very many instances present, and in these cases the memory savings may become significant, especially if there are many flags present in each instance.

How many is very many? There are algorithms in the products I work on that may manipulate many millions of such instances. In these cases every byte counts to maximise in-memory instance working sets and to optimise persistent store I/O and in-memory manipulations of sets of instances.

Using this approach smooths the code on the calling side, while maintaining optimal memory & storage use for the flags on the implementation side. In reality, the only overhead is that of a method call as the bit flag testing must be done at some point.

Another option to implement a series of flags is as a set (even if the individual items don't relate to one another as they would normally when declaring a set). This is really a misuse of the way sets are implemented in Delphi, and is at the mercy of set implementation rules in terms of the quantity of memory used to store the set. Similarly, in order to hide the fact the flags are implemented as a set (to avoid forcing the client to use set operators to access individual flags), there would still be a need to implement properties in the way I have described here.

Larger collections of bitflags is the topic of another blog.

Note: This is but one use of the very useful index modifier in property declarations.

Regards,
Raymond.

No comments: