Proper way to do const std::string in a header file?

24,129

Solution 1

The code you wrote is perfectly fine, at least as you only #include the Constants.h file in only one source file. If you use the header file in multiple source files, you will have the same variables defined multiple times. The correct use of constants in header files are to split them into a header (Constants.h) which contains the declarations of the variables, and a source file (Constants.cpp) which contains the definitions of the variables:

The header file:

#ifndef CONSTANTS_H
#define CONSTANTS_H

extern const std::string kAttributeX;
extern const std::string kAttributeY;

#endif

The source file:

const std::string kAttributeX = "x";
const std::string kAttributeY = "y";

Solution 2

Your second option causes each of the variables to be created in every translation unit (cpp file that includes the header) which will slightly increase code size as well as adding a bit of runtime cost (constructing all those string during launch and destructing them during process termination).

The solution suggested by Joachim works but I find declaring and defining variables separately to be a bit of a drag. I personally hate repeating myself, also I don't like saying the same thing over and over again...

I don't know of a good solution for this in C++ proper but the compilers I've worked with all support something like __declspec( selectany ) so that you can define the variable in the header file and only get one object instantiated (rather than one for each translation unit).

__declspec( selectany ) extern const std::string kAttributeX = "x";

(For why both extern and const see this answer).

You still have the drawback of paying the initialization price of all the global variables during process launch. This is acceptable 101% of the time (give or take 2%) but you can avoid this by using lazy objects (I've written about something similar here).

Share:
24,129
Nathanael Weiss
Author by

Nathanael Weiss

Author of a Cocos2d-X book and an iPhone RPG engine.

Updated on February 23, 2020

Comments

  • Nathanael Weiss
    Nathanael Weiss over 4 years

    I'm writing a Cocos2D-X game where the player, enemies and other characters store their attributes in a CCMutableDictionary, which is somewhat of a decorator class for std::map<std::string, CCObject*>. A value in the dictionary can be accessed via the CCMutableDictionary::objectForKey(const std::string& key) method.

    Now, in a header file included by many of my .cpp files, I've got a few const char * const strings for accessing values in the dictionaries, like this:

    // in Constants.h
    const char* const kAttributeX = "x";
    const char* const kAttributeY = "y";
    
    // in a .cpp file
    CCObject* x = someDictionary->objectForKey(kAttributeX);
    

    So, correct me if I'm wrong, but std::string's copy constructor is being called and a temporary std::string is on the stack every time I call one of the above objectForKey methods using a const char* const, right?

    If so, I feel that it would be more efficient at runtime if those constant attribute keys were already std::string objects. But how do I do that the right way?

    Defining them in the Constants.h file like the following compiles fine, but I have a feeling that something just isn't right:

    // in Constants.h
    const std::string kAttributeX = "x";
    const std::string kAttributeY = "y";
    

    My apologies if this question has already been asked. I couldn't seem to find the exact answer I was looking for here on StackOverflow.

    • Andy Krouwel
      Andy Krouwel almost 5 years
      Be careful when replacing 'const char* const' with 'const std::string'. The initialisation order of static objects is undefined, so it can have very unpleasant consequences if you use your const string to initialise other static/const objects before it's actually initialised itself. This is not a problem with const char* const, which is guaranteed to exist before any execution starts. This situation is a nightmare to debug too because the code looks correct.
  • Nathanael Weiss
    Nathanael Weiss about 12 years
    So, if the strings are defined in a .cpp file, when are they actually instantiated?
  • Some programmer dude
    Some programmer dude about 12 years
    @NatWeiss They will be instantiated together with all the other global variables.
  • Brent212
    Brent212 over 9 years
    Does the issue with the variables defined multiple times apply when they were "const char* const"? I'm pretty sure it doesn't, but I don't understand why. What gives "const char* const" the privilege of being defined in a single location that includes its value (much nicer, IMO, especially in terms of maintenance)?
  • Ferruccio
    Ferruccio over 9 years
    You could also use static const std::string kAttributeX = "x"; in the header. Then you won't need to instantiate those constants in a source file. Each compilation unit which #includes the header will get its own copy of the constant object, but since it's not externally visible, there won't be any duplicate symbol errors.
  • peetonn
    peetonn almost 8 years
    I'd add just for completeness, if your const variables defined under a namespace, you need to re-open it in the source file.
  • Technophile
    Technophile almost 5 years
    To automate separate declaration and definition (if you are OK using the 'C' preprocessor) try X macros: en.wikipedia.org/wiki/X_Macro or drdobbs.com/cpp/the-x-macro/228700289.
  • Caroline Beltran
    Caroline Beltran over 4 years
    @Someprogrammerdude, I've been using the following technique with VC++ 2017: static constexpr char* kAttributeX { "xxxxxx" }; but with GCC, I get a warning "ISO C++ forbids converting a string constant to ‘char*’". The good thing about using the static constexpr char* is that I can include the header everywhere, and no cpp to maintain. Unfortunately, I get the GCC warning. Since I don't know what is going on behind the scenes, do you think that it is OK to disable the warnings in GCC?
  • Some programmer dude
    Some programmer dude over 4 years
    @CarolineBeltran constexpr char*means that the pointer is a constexpr but not what it's pointing to. And in C++ all string literals are constant arrays of characters, so you must have a pointer to const data (as in char const*, a.k.a. const char*, or if you want the pointer to be constant as well char const* const).
  • nybon
    nybon over 2 years
    If you use this approach, and assign this const variable to another static variable in a different cpp file, you may run into the static initialization order fiasco like this (stackoverflow.com/questions/46562265/…), which is undesirable and difficult to troubleshoot