1

In my code, I am using string IDs. That is good in debug and in code during coding. You can imagine it like this:

MyCar * c = cars->GetCarID("my_car_1");
MyCar * c = cars->GetCarID(variable);

But this is slower in release, because of string - string comparison in GetCarID code. I would like to have something like this

MyCar * c = cars->GetCarID(CREATE_ID("my_car_1"));
MyCar * c = cars->GetCarID(CREATE_ID(variable));

CREATE_ID - in debug, it will return string that is written in code, in release, it will return int hash or something like that.

How can I achieve this? Or how is this usually solved?

Martin Perry
  • 9,232
  • 8
  • 46
  • 114

4 Answers4

3

You can wrap your id in a class, something like:

class StringID
{
    public:
        StringID(const StringID&);
        StringID(const char*);
        StringID(const std::string&);

    ...

    private:

#if DEBUG
        std::string _idAsString;
#endif
        int         _id;
};

and define common operation for your class, like operator<, operator== and so on. In this way on release build you will have a wrapper over int and on debug build the class will contain the string to make it easier to debug.

For performance reasons you can make id/hash computation constexprand compute it at compile time for string literals (for more info please check this: Computing length of a C string at compile time. Is this really a constexpr?).

With this approach you can catch hash clashes by checking also the strings in debug mode, not only the hashes. As you probably already know, different strings can lead to same hash (unlikely for common english words, but possible) and this will be very difficult to debug without knowing the string that generated the hash.

Community
  • 1
  • 1
Mircea Ispas
  • 20,260
  • 32
  • 123
  • 211
2

In debug mode, #define CREATE_ID(x) #x and use cars->GetCarID(CREATE_ID(my_car_1));. In release mode, #define CREATE_ID(x) x, add an enum { my_car_1, ... } and you still use cars->GetCarID(CREATE_ID(my_car_1));

Note that you never use CREATE_ID(variable) but you may use auto variable = CREATE_ID(my_car_id).

MSalters
  • 173,980
  • 10
  • 155
  • 350
  • First part for compile-time IDs is good, problem is that some of the IDs are code generated or read from user input. – Martin Perry Feb 18 '15 at 08:29
  • Code generation is no big deal - just generate `CREATE_ID(x)`. It's still passed to the compiler. The problem is of course user input, which isn't passed to a compiler. That really should have been in the question, since it means you can't use integer ID's everywhere. You need to map those enum names to strings and back - check StackOverflow for suggestions. – MSalters Feb 18 '15 at 08:36
0

You could use enums instead of strings. This way you have readable names for your integers:

enum ID {
      my_car_1
    , my_train_1
    , my_g6_1
};
Maxim Egorushkin
  • 131,725
  • 17
  • 180
  • 271
-1

My approach would be to use enum class. This allows to have typesafe identifier, that is char or int underneath. A simple code example would be:

#include <iostream>
enum class COLOR {
      RED
    , GREEN
};

std::ostream & operator << (std::ostream & o, const COLOR & a) {
    switch(a) {
        case COLOR::RED: o << "RED";break;
        case COLOR::GREEN: o << "GREEN";break;
        default: o << static_cast<int>(a);
    }
    return o;
}
int main() {
    COLOR c = COLOR::RED;
    std::cout << c << std::endl;
    return 0;
}

The drawback is, that you have to explicitly writeout all identifiers twice - once in class and once in operator.

One of the mayor advantages of enum class is that names are scoped and it does not allow stuff like:

std::string get(COLOR x);
...
get(3); //Compile time error
get(CAR::GOLF);//Compile time error

Judging by your comments on other answers, you can define your own identifier like this:

class MyId {
public:
   int id; 
   static std::unordered_map<int,std::string> names; 
};
std::ostream & operator << (std::ostream &o,const MyId & m) {
    auto itr = MyId::names.find(m.id);
    if(itr!= MyId::names.end()) {
        o << itr->second;
    } else { 
        o << "Unknown " << m.id;
    }
    return o;
}

Its typesafe, theres no more overhead then int, and it is able to survive user input.

UldisK
  • 1,619
  • 1
  • 17
  • 25
  • 1
    Hm, how is that different to what I proposed? You probably want `char const* toString(COLOR)` instead of `std::string get(COLOR)` to avoid the unnecessary memory allocation and data copy resulting from your use of `std::string` here. – Maxim Egorushkin Feb 18 '15 at 10:05
  • @MaximEgorushkin the get part was only an example of `enum class` typesefty, it was not intended as get name or smth. There is and ostream operator for getting name for logging purposes. – UldisK Feb 18 '15 at 10:08
  • You use enumeration as I advised. The rest is just implementation details. Before you got advise here you had no clue. – Maxim Egorushkin Feb 18 '15 at 10:11