[LC++]enum, #define, const, static const, or member const???

Mark Phillips mark at austrics.com.au
Mon Apr 8 17:15:06 UTC 2002


Carlo Wood wrote:


> With "Just use an integer" I mean (and assumed you did mean) code
> like:
> 
>    int kind;
> 
>    if (kind == 3)
>     ...
>    else if (kind == 7)
>     ...
> 
> Note the literal '3' and '7'.
> 
> Surely you understand why that is bad.


Yes, sorry, I was thinking you mean't "Just use an integer to
encode the kind" was bad.


>>enum ShortRange {
>>   srPredex=-1, srDeb=0,
>>   srZero=0, srOne=1, srTwo=2,
>>   srFin=2, srPodex=3};
>>
>>Then you effectively get a subrange of the integers, with the
>>advantages you mention above, __and__ you get your own separate
>>type.  (The values srZero, srOne and srTwo are the "normal usage
>>values", whereas srPredex and srPodex are "only to be used in special
>>circumstances, like with index variables".)
>>
> 
> First of all, this are enums, not literal integers.  So the "Bad idea"
> phrase that I used doesn't refer to this case.  I had a seperate
> section about enums.
> 
> That section stressed to use CLEAR names.  Surely "srZero", "srOne"
> and "srTwo" are very bad names ;).  If that would be everything they
> meant then you should use a normal integer of course.


Not necessarily I think.  You might want your own "exclusive range of
integers, only to be used in certain ways".  The idea is that you could
convert between usual integers and exclusive ones, but only through
a cast.  It is a mechanism for saying "this integer has a slightly
different type from that integer, but both behave like integers
nevertheless".


>>Below you seem to say that this method is good except not very
>>maintainable.  Why?  Suppose I wanted to extend it to include
>>-1 and 3 as "to be used" values.  I would just do:
>>
>>enum ShortRange {
>>   srPredex=-2, srDeb=-1
>>   srMiOne=-1, srZero=0, srOne=1, srTwo=2, srThree=3,
>>   srFin=3, srPodex=4
>>};
>>
> 
> And have srDeb == srMiOne ?


Yes.  Conceptually ShortRange is comprised of three parts:

1. The usual data range (index set):

    srMiOne=-1, srZero=0, srOne=1, srTwo=2, srThree=3

    These are the concrete values that usually ShortRange data
    variables will take on.

2. The augmented values:

    srPredex=-2, srPodex=4

    These are special values, the predex is one before the index
    set and podex is one after.  They allow you to write things
    like:

      ShortRange i=srZero;
      for (; i<srPodex; ++i)
        if (okay(i))
          // do something
        else break;
      if (i<srPodex)
        cout<<"Terminated prematurely at position "<<i<<endl;

3. The deb and fin labels:

    srDeb=-1, srFin=3

    These should not be thought of as additional elements of the
    index set, but rather as alternative labels for the first and
    last elements respectively of the range.  They should be used
    with that understanding in mind.  For example:

      for (ShortRange i=srDeb; i<=srFin; ++i)
        process(data(i));

    This allows one to refer to the conceptual notion of first and
    last in the list without typing in the _actual_ first and last
    elements --- meaning code is more maintainable.


> If { srMiOne, srZero, srOne, srTwo, srThree } are a subgroup
> and { srDeb } is a subgroup, then these two shouldn't overlap,
> that could cause confusion.  


This is only a problem if the _meaning_ of ShortRange is misunderstood.
If srDeb is _understood_ to be an alternative label for the first
element of the index set (not including srPredex), then these subgroups
rightly overlap.

> For example:
> 
> ShortRange data[] = { srTwo, srMiOne, srThree, srThree, srOne, srFin };
> 
> ...
> 
>   for (int i = 0; data[i] != srFin; ++i)
>     ..
> 
> would fail suddenly after you proposed addition.


This would fail even before the modification of ShortRange, because
srFin is not the right thing to use here.  srPodex or srPredex should
have been used instead.  (Or perhaps a new addition like srSpecial.)


> As I described in my previous post, you can do that.
> Let me give an example of code that I wrote myself
> (from http://libcwd.sourceforge.net)
> 
> // The different instance-ids used in libcwd.
> enum mutex_instance_nt {


I like your method of breaking things into sections!

>   // Recursive mutexes.
>   tsd_initialization_instance,
>   object_files_instance,        // rwlock
>   end_recursive_types,
>   // Fast mutexes.
>   memblk_map_instance,
>   mutex_initialization_instance,
>   ids_singleton_tct_S_ids_instance,
>   alloc_tag_desc_instance,
>   type_info_of_instance,
>   dlopen_map_instance,
>   write_max_len_instance,
>   set_ostream_instance,
>   debug_objects_instance,       // rwlock
>   debug_channels_instance,      // rwlock
>   // Values reserved for read/write locks.
>   reserved_instance_low,
>   reserved_instance_high = 3 * reserved_instance_low,


But why do 3* ?


>   // Values reserved for test executables.
>   test_instance0 = reserved_instance_high,
>   test_instance1,
>   test_instance2,
>   test_instance3,
>   instance_locked_size          // Must be last in list
> };


>>With the setup I used with my "ShortRange" type, surely it
>>would be safe to use them like integers?
>>
> 
> Well, no.  But lets say you need them as array index, then 
> you clearly can't change them later (or you'd need to make
> sure that all arrays put the corresponding elements in the
> right place and that is not maintainable).


I'm not sure what you mean by "can't change them".  You can
certainly do something like this:

int const srSize=srPodex-srDeb;

string data[srSize];

for (int i=0; i<srSize; ++i)
   process(data[srDeb+i]);

and then changes may be made safely.  (The only thing you
would need to be careful with is if you wrote these structures
directly in and out of files.  The way to safeguard against
this sort of problem would be to store the enum type to file too.)


> Then, I suppose, you could do something like you did.
> You shouldn't use the same values for two different elements
> though :/.  Also, it is must more likely that each element
> of the array has a special meaning.  Can't you use names
> that reflect THAT meaning?  And if they don't, then why do
> you need to pass the index around?


Yes, sometimes you do have names which reflect a meaning
distinct from that of integers.  In this case you might
do something like:

enum Colour {
   cPredex=-1, cDeb=0;
   cRed=0, cGreen=1, cBlue=2,
   cFin=2, cPodex=3
};

This would allow you to use the colours as non-integral
values when that was appropriate, but on occasion when you
wanted to treat them as equivalent to an integral range,
you could do so in a safe way.


> Let me make up an example.
> 


...snip...


> 
> At some point you just have to stop putting data
> into enums and choose a clear design like:


...snip...

Yes, what you say seems sensible, though an alternative
approach would be:

enum PieceKind {
   pkPredex=-1, pkDeb=0,
   pawn=0, bishop=1, knight=2, rook=3, queen=4, king=5,
   pkFin=5, pkPodex=6
};

int const pkSize=pkPodex-pkDeb;

and then continue on the same way you did.


> Personally I only use enums for something that literally means
> "instance" of something and preferably not as array index,
> it is possible though I just don't like the feeling.
> I DO use the fact that enums are ordered, so you can savely
> know that blah < foo, and will STAY smaller independent of
> the additions made later.  That means you can make at least
> two subgroups and often three or more, depending on their
> mutual relationship.  It also allows you to use 'markers'
> that signify the start/end of a subgroup.
> The problem with indexes is that they are ALL a subgroup
> on their own AND need a fixed value: you can't insert
> a value later without shifting data in all arrays that use
> them, that is bad.


Why is this bad?  If all the data handling routines use the
enums appropriately, the data will all shift perfectly without
error.


>>Isn't "static int const" equivalent to "int const"?  Ie doesn't
>>the latter default to static?
>>
> 
> no.  If your compiler does that then it is non conforming to ISO C++.
> 
> int const x = 3;		// Exported.
> static int const y = 4;		// Not exported.


Does that mean x will have an address by default?  I thought that
unless the address of x is asked for, it will inline the value 3.
If I'm right then "int const" will behave the same as "static
int const" except in the case of the address of x being referred
to.  Is this right?

Cheers,

and thanks again for your help,

Mark.





More information about the tuxCPProgramming mailing list