5

In C++, if I want to define some non-local const string which can be used in different classes, functions, files, the approaches that I know are:

  1. use define directives, e.g.

    #define STR_VALUE "some_string_value"
    
  2. const class member variable, e.g.

    class Demo {  
    public:  
      static const std::string ConstStrVal;  
    };  
    // then in cpp  
    std::string Demo::ConstStrVal = "some_string_value";  
    
  3. const class member function, e.g.

    class Demo{  
    public:  
      static const std::string GetValue(){return "some_string_value";}  
    };  
    

Now what I am not clear is, if we use the 2nd approach, is the variable ConstStrVal always initialized to "some_string_value" before it is actually used by any code in any case? Im concerned about this because of the "static initialization order fiasco". If this issue is valid, why is everybody using the 2nd approach?

Which is the best approach, 2 or 3? I know that #define directives have no respect of scope, most people don't recommend it.

Thanks!

Martijn Pieters
  • 1,048,767
  • 296
  • 4,058
  • 3,343
Hunter
  • 151
  • 1
  • 14

4 Answers4

5

if we use the 2nd approach, is the variable ConstStrVal always initialized to "some_string_value" before it is actually used by any code in any case?

No

It depends on the value it's initialized to, and the order of initialization. ConstStrVal has a global constructor.

Consider adding another global object with a constructor:

static const std::string ConstStrVal2(ConstStrVal);

The order is not defined by the language, and ConstStrVal2's constructor may be called before ConstStrVal has been constructed.

The initialization order can vary for a number of reasons, but it's often specified by your toolchain. Altering the order of linked object files could (for example) change the order of your image's initialization and then the error would surface.

why is everybody using the 2nd approach?

many people use other approaches for very good reasons…

Which is the best approach, 2 or 3?

Number 3. You can also avoid multiple constructions like so:

class Demo {
public:  
  static const std::string& GetValue() {
    // this is constructed exactly once, when the function is first called
    static const std::string s("some_string_value");
    return s;
  }  
};  

caution: this is approach is still capable of the initialization problem seen in ConstStrVal2(ConstStrVal). however, you have more control over initialization order and it's an easier problem to solve portably when compared to objects with global constructors.

justin
  • 104,054
  • 14
  • 179
  • 226
  • 1
    I like this answer. One thing I don't understand is, I've searched for the answer in stack overflow for a while, most people mention approach 2, but rarely do people memtion approach 3. – Hunter Apr 21 '12 at 06:21
  • @Hunter its name is "construct on first use". hope that helps you research it further. – justin Apr 21 '12 at 06:28
1

In general, I (and many others) prefer to use functions to return values rather than variables, because functions give greater flexibility for future enhancement. Remember that most of the time spent on a successful software project is maintaining and enhancing the code, not writing it in the first place. It's hard to predict if your constant today might not be a compile time constant tomorrow. Maybe it will be read from a configuration file some day.

So I recommend approach 3 because it does what you want today and leaves more flexibility for the future.

Old Pro
  • 24,624
  • 7
  • 58
  • 106
0

Avoid using the preprocessor with C++. Also, why would you have a string in a class, but need it in other classes? I would re-evaluate your class design to allow better encapsulation. If you absolutely need this global string then I would consider adding a globals.h/cpp module and then declare/define string there as:

const char* const kMyErrorMsg = "This is my error message!";
keelerjr12
  • 1,693
  • 2
  • 19
  • 35
  • I don't think throwing const string in global scope is good idea. A scoped const should be better, namespace or class. In this case, class is a way to limit the string in a scope so that it won't pollute global space, but the purpose is to allow those who needs it be able to access it. – Hunter Apr 21 '12 at 06:10
  • Yes, it is better to limit the scope if you can. Meaning does it make sense to store the string within a specific class. I guess it all depends on what you're trying to do with it and what class you're sticking it in. Also, you don't need a string object for this, just use a C-string. – keelerjr12 Apr 21 '12 at 06:17
  • why not, we use static member variables/functions, so there is no object constructor overhead. What if you want to define all kinds of err message in one class, and let 10 other classes use the message, but you don't want the rest of 900 classes know it? – Hunter Apr 21 '12 at 06:26
0

Don't use preprocessor directives in C++, unless you're trying to achieve a holy purpose that can't possibly be achieved any other way.

From the standard (3.6.2):

Objects with static storage duration (3.7.1) shall be zero-initialized (8.5) before any other initialization takes place. A reference with static storage duration and an object of POD type with static storage duration can be initialized with a constant expression (5.19); this is called constant initialization. Together, zero-initialization and constant initialization are called static initialization; all other initialization is dynamic initialization. Static initialization shall be performed before any dynamic initialization takes place. Dynamic initialization of an object is either ordered or unordered. Definitions of explicitly specialized class template static data members have ordered initialization. Other class template static data members (i.e., implicitly or explicitly instantiated specializations) have unordered initialization. Other objects defined in namespace scope have ordered initialization. Objects defined within a single translation unit and with ordered initialization shall be initialized in the order of their definitions in the translation unit. The order of initialization is unspecified for objects with unordered initialization and for objects defined in different translation units.

So, the fate of 2 depends on whether your variable is static initialised or dynamic initialised. For instance, in your concrete example, if you use const char * Demo::ConstStrVal = "some_string_value"; (better yet const char Demo::ConstStrVal[] if the value will stay constant in the program) you can be sure that it will be initialised no matter what. With a std::string, you can't be sure since it's not a POD type (I'm not dead sure on this one, but fairly sure).

3rd method allows you to be sure and the method in Justin's answer makes sure that there are no unnecessary constructions. Though keep in mind that the static method has a hidden overhead of checking whether or not the variable is already initialised on every call. If you're returning a simple constant, just returning your value is definitely faster since the function will probably be inlined.

All of that said, try to write your programs so as not to rely on static initialisation. Static variables are best regarded as a convenience, they aren't convenient any more when you have to juggle their initialisation orders.

enobayram
  • 4,650
  • 23
  • 36
  • standard documentation is always clear and helpful! Thanks a lot! Both yours and justins are good answers. – Hunter Apr 21 '12 at 07:03