Advertisement

How to organize constants? (c++)

Started by August 09, 2018 01:38 PM
7 comments, last by suliman 6 years, 3 months ago

Hi

I have loads of constants for different game-states, groups, actions, item classes, armour types etc. I usually just use


#define ICLASS_SWORD 1
#define ICLASS_AXE 2

#define ACTION_SKIPTURN 0
#define ACTION_MOVE 1

#define ARMOUR_LIGHT 0
#define ARMOUR_MEDIUM 1

And so on. While this works, it seems a bit primitive :) Also sometimes (eg my latest project) i end up with well over a hundred of these. Is there a more clever way to do this? I know about enums but it's still similar usage.

I still have config files (txt-files) which holds most of the data (eg all data about each sword and axe, all the skills and actions etc) but i need to be able to reference these things in the code itself and dont want to use magic constants (obviously).

Any better idea?


//this is horrible (magic constant)
if (unit.action == 1)
   unit.move();

//this is better (what I use now for most things)
if (unit.action == ACTION_MOVE)
   unit.move();

 

In modern C++, the normal way to do that would be a strongly typed enum like so:


enum class WeaponClass {
  Sword,
  Axe
};

if(weapon.class == WeaponClass::Axe) {
  ...
}

 

Advertisement

If it's meant to be used as a numeric constant, you might want to use something like const_expr


const_expr int ITEM_SLOT_SIZE = 6;

You can use const_expr for things that require constant value at the compile time,in the same fashion as #define (well technically not the same, as #define will be changed to an actual value during in the preprocessor processing), like array declaration. However it has an advantage as you can check the value during debugging (as oppose to #define macro). 

However I don't think you should use either #define or const integer for something that not really a number, but a set of abstract values (eg. day of the week, action of character, type of weapon etc.) ... (well everything in computer science are made of numbers though, this kinda false in a way).

Consider something like


if (unit.action == ARMOUR_LIGHT) // what kind of action here ?? 
  unit.move();

If unit.action() is in fact an integer, the boolean expression can be evaluated. So the expression could be true or false depends on what the actual value is, but I wouldn't know what kind of behavior this should be. In this case use the class enum as @rnlf_in_space mentioned is much better.

14 minutes ago, rnlf_in_space said:

In modern C++, the normal way to do that would be a strongly typed enum like so:



enum class WeaponClass {
  Sword,
  Axe
};

if(weapon.class == WeaponClass::Axe) {
  ...
}

http://9tawan.net/en/

9 minutes ago, mr_tawan said:

If it's meant to be used as a numeric constant, you might want to use something like const_expr

For one, it's called constexpr in a single word. Second, it the advantage of using a typed enum is type safety. Using integers for everything makes the code less easy to understand and easier to confuse different things:


constexpr int WEAPON_AXE = 1;
constexpr int WEAPON_SWORD = 2;

constexpr int ACTION_MOVE = 3;

if(unit.action == WEAPON_AXE) { 
  // allowed by the compiler, but clearly not what you want.
  // something like this will happen sooner or later. Maybe not in an if statement, but
  // I've seen it happen dozens of times when passing method arguments.
  // And when it happens, it can be a pain to debug.
}


enum class WeaponClass {
  Axe,
  Sword
};


enum class Action {
  Move,
  SkipTurn
};

if(unit.action == WeaponClass:Axe) // compiler error

 

If you really want to express a number (like mr_tawan's example in the beginning), then a constexpr is a good way to do it. You'll have to make it a static constexpr though, if you want to define it in a header file, or else you will get into trouble when linking.

4 minutes ago, rnlf_in_space said:

For one, it's called constexpr in a single word.

That was embarrassing of me. I'm sorry about that.

http://9tawan.net/en/

Fair enough. But why not "normal" enum? Seems more condensed to code:
 


enum Weapontype { sword, axe };

Weapontype w = sword;

if (w == sword)
   doSwordDance();

 

Advertisement

suliman, because it doesn't protect you from type errors.


enum WeaponClass {
  Axe,
  Sword
};


enum Action {
  Move,
  SkipTurn
};

if(unit.action == Axe) // perfectly fine, but still not what you want

And in the end you'll want to prefix the names, because now they are not scoped anymore, imagine this:


enum WeaponClass {
  Axe,
  Sword,
};

enum ImpactSound {
  Axe,   // error, Axe already defined
  Sword  // error, Sword already defined
};

The classic way to fix that would be to prefix the enumerators:


enum WeaponClass {
  WeaponAxe,
  WeaponSword,
};

enum ImpactSound {
  ImpactSoundAxe, 
  ImpactSoundSword
};

And that's already almost the scoped/typed enum variant.

Ok thanks for clearing that up!

This topic is closed to new replies.

Advertisement