views:

258

answers:

5

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?
+7  A: 

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).

paxdiablo
Upvotes all around!
Chris Lutz
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.
caspin
+5  A: 

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.

Chris Lutz
+1 you beat me by 24 seconds :(
D.Shawley
+17  A: 

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.)

AndreyT
`extern` on the last definition?
GMan
@GMan: It is certainly optional.
AndreyT
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?
GMan
@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).
AndreyT
Ah, I see, makes sense.
GMan
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.
caspin
@Caspin: It does not work if you use one such object to initialize another such object. The issue indeed exists. But it is a different story.
AndreyT
+1  A: 

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.

Potatoswatter
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.
caspin
@Caspin: It's thread safe if you initialize it before there is contention.
Potatoswatter
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.
caspin
+1  A: 

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.

Permaquid
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.
caspin
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.
caspin