19

I want a header file with a non-integral constant in it, e.g. a class. Note the constant does not need to be a compile-time constant.

static const std::string Ten = "10";

This compiles but is undesirable as each compilation unit now has its own copy of Ten.

const std::string Ten = "10";

This will compile but will fail with a linker error for multiply defined Ten.

constexpr std::string Ten = "10"s;

This would work but only if the strings constructor was constexpr as well. It will be but I can't count on every non-integral constant to have a constexpr constructor ... or can I?

extern const std::string Ten = "10";

This seems to work but I'm afraid I'll get a linker error if I breath on it wrong.

inline const std::string Ten( ) { return "10"; }

This has everything I want except a clean syntax. Plus now I have to refer the constant as a function call, Ten().

inline const std::string = "10";

This seems to be the ideal solution. Of course inline variables aren't allowed by the standard.

  • Is there something in the c++ standard that says the extern version should work or am I just lucky it works with GCC?
  • Is there a compelling reason not to allow inline variables?
  • Is there a better way with c++03 or will there be a better way in c++0x?
6
  • You say you want "a file" to have it, but then say it's undesirable since it's in more than one file. Which is it?
    – GManNickG
    Commented Jan 28, 2010 at 2:14
  • 1
    What would an "inline variable" do that a plain ol' constant wouldn't?
    – Anon.
    Commented Jan 28, 2010 at 2:15
  • Caspin, why is it undesirable to have each compilation unit have its own copy of Ten? Consider that the "inline const std::string Ten()" version will always return a new, separate object on each call anyway. Commented Jan 28, 2010 at 2:44
  • @Stefan - Bloat. It's not significant, but it's still unnecessary bloat, and is usually an indication of "doing it wrong." The OP presumably sees that he's "doing it wrong" and, like a rational person, wants to know the right way.
    – Chris Lutz
    Commented Jan 28, 2010 at 2:51
  • Chris, ok but where exactly is the bloat? Executable size? Commented Jan 28, 2010 at 4:05

5 Answers 5

32

You seem to have them mixed up.

You are right about

static const std::string Ten = "10"; 

version. It will "work", but it will create a separate object in each translation unit.

The version without static will have the same effect. It won't produce linker errors, but will define a separate object in each translation unit. In C++ language const objects have internal linkage by default, meaning that

const std::string Ten = "10"; // `static` is optional

is exactly equivalent to the previous version with static.

The version with extern and initializer

extern const std::string Ten = "10"; // it's a definition!

will produce a definition of an object with external linkage (it is a definition because of the presence of an initializer). This version will result in linker errors, since you'll end up with multiple definitions of an object with external linkage - a violation of ODR.

Here's how you can do it:

In order to achieve what you are trying to achieve, you have to declare your constant in the header file

extern const std::string Ten; // non-defining declaration

and then define it (with initializer) in one and only one of the implementation files

extern const std::string Ten = "10"; // definition, `extern` optional

(If the constant is pre-declared as extern, then extern in the definition is optional. Even without an explicit extern it will define a const object with external linkage.)

8
  • extern on the last definition?
    – GManNickG
    Commented Jan 28, 2010 at 2:27
  • @GMan: It is certainly optional. Commented Jan 28, 2010 at 2:33
  • Ah, okay. Just read the first half of your post and didn't know you could define with extern on it. What's the purpose of that?
    – GManNickG
    Commented Jan 28, 2010 at 2:46
  • 5
    @Gman: Without extern it would define a const object with internal linkage. However, the presence of the previous extern declaration overrides that. So the extern in the definition is optional. I put it there "just in case". Some might argue that it improves readability a little, since it makes it explicitly clear that it is defining a const object with external linkage. Without extern a person not aware of the previous declaration might conclude that linkage is internal. But again, it is optional in this case (because of the previous declaration). Commented Jan 28, 2010 at 3:00
  • This solution does not work in the general case! Static initialization order messes this up. If constant 'A' is defined in one.cpp but use to initialize constant 'B' in another.cpp. The intialization may not be correct meaning B will be initialized using a yet to be constructed A.
    – deft_code
    Commented Feb 8, 2010 at 18:18
10

I don't know if there's a better way in C++ but the best way in C (which will also work in C++) is one of the ones you've listed.

Have a separate compilation unit (eg,ten.cpp) holding just the data:

const std::string Ten = "10";

and a header file (eg,ten.h) declaring it so it can be used elsewhere:

extern const std::string Ten;

Then you just have to ensure any compilation unit that wants to use it include the header file (eg,ten.h), and any executable that wants to use it link with the separate compilation unit (eg,ten.o).

This gives you one copy of the variable, accessible anywhere. Of course, you could just define it in the header file as static and have one copy per compilation unit. That would simplify what files you need to have and the static would ensure there's no doubly-defined symbols. But that's not something I'd ever recommend.

I don't know why you state:

but I'm afraid I'll get a linker error if I breath on it wrong

This is accepted practice from long ago and you should know how all these things fit together if you wish to call yourself a C++ programmer (no insult intended).

1
  • I thought I was only declaring the constant Ten in a header file without any definition to back it up. If at any time the compiler decided it needed the address of the extern it would find that I never gave it one by actually defining the variable in a source file. It turns out I was wrong. Providing an extern variable with initialization is providing the definition.
    – deft_code
    Commented Jan 28, 2010 at 6:50
6

The extern version is close to what you want. Here:

// in the file tenconstant.cpp
const std::string Ten = "10";

// in the file tenconstant.h
extern const std::string Ten;

// in your file
#include "tenconstant.h"

// do stuff with Ten

You need it to be defined once for the linker, which is the purpose of myconstants.cpp, but declared everywhere you use it, which is the purpose of myconstants.h. This may seem a bit unwieldy for one variable, but for a larger project, you will probably have a nice header that gets used a lot that you can stick this in.

0
2

It is a bad idea to create a static user-defined type in this way. You can't control the order of instantiation when you have multiple such UDTs. This is not a problem in a small project, but not all projects are small. It's a better idea to make your statics all plain old data types - raw pointers - and to initialize them in some appropriate way to point at the instances you need when the program starts up, or when you need them. This puts you in control.

Your question stated that these types do not need to be compile-time constants. If so, and you have a multi-threaded program, your objects need to have their state protected from simultaneous access from multiple threads. If some of your objects are not thread-safe, then in addition to the object itself you need a mutex object to protect its state, and that has to have the same linkage, and will need initialization. All this complicates the global state of your program in what could be an unacceptable way.

2
  • The constants are well constant. Generally it is safe to have multiple threads read a constant. The usage above is completely safe in the java memory model. I'm pretty sure it will be in c++0x as well. The only exception to this is if someone somewhere might modify the value. If that happens all bets are off. I assume I'm safe from that by making it const.
    – deft_code
    Commented Jan 28, 2010 at 6:22
  • I just got bit by initialization order. Some of constants weren't yet constructed when they were used to initialize other constants. Aargh! This is soo much harder than it should be.
    – deft_code
    Commented Feb 8, 2010 at 18:22
1

I think the other answers here are better, but if you're dead-set on doing it all with headers, you can effectively inline your object (as you specifically ask) with a simple wrapper function.

inline const std::string &get_ten() {
    static const std::string ten = "10";
    return ten;
}

There will be only one string, initialized once, and you don't need anything outside of the header file.

3
  • This is essentially the same thing that I proposed with the simple inline function. The advantage of this approach is that the memory is well defined. However, it not thread safe.
    – deft_code
    Commented Jan 28, 2010 at 6:23
  • @Caspin: It's thread safe if you initialize it before there is contention. Commented Jan 28, 2010 at 7:52
  • It turns out that all the other answers cannot handle the static initialization order issue. If constant 'A' is defined in one.cpp but use to initialize constant 'B' in another.cpp. The intialization may not be correct meaning B will be initialized using a yet to be constructed A.
    – deft_code
    Commented Feb 8, 2010 at 18:17

Not the answer you're looking for? Browse other questions tagged or ask your own question.