If you want to associate some constant value with a class, here are two ways to accomplish the same goal:
class Foo
{
public:
static const size_t Life = 42;
};
class Bar
{
public:
enum {Life = 42};
};
Syntactically and semantically they appear to be identical from the client's point of view:
size_t fooLife = Foo::Life;
size_t barLife = Bar::Life;
Is there any reason other than just pure style concerns why one would be preferable to another?
-
They're not identical:
size_t *pLife1 = &Foo::Life; size_t *pLife2 = &Bar::Life;Konrad Rudolph : Essentially what I said, in less words. ;-)John Dibling : See, that's why I like Stack Overflow...jwfearn : Mike F, I assume that the scond line is supposed to generate a compile error. Perhaps you could add a comment stating that.Johannes Schaub - litb : yes, and you should change it to size_t const* pLife1 -
The
enumhack used to be necessary because many compilers didn't support in-place initialization of the value. Since this is no longer an issue, go for the other option. Modern compilers are also capable of optimizing this constant so that no storage space is required for it.The only reason for not using the
static constvariant is if you want to forbid taking the address of the value: you can't take an address of anenumvalue while you can take the address of a constant (and this would prompt the compiler to reserve space for the value after all, but only if its address is really taken).Additionally, the taking of the address will yield a link-time error unless the constant is explicitly defined as well. Notice that it can still be initialized at the site of declaration:
struct foo { static int const bar = 42; // Declaration, initialization. }; int const foo::bar; // Definition.Richard Corden : Actually - your compiler shouldn't allocate any storage space for the constant. The standard explicitly states that only if the object is "used", ie. it's address needed, is the definition for the object required. And then the developer has to provide a definition explicitly!Richard Corden : If you take the address of a "static const" but don't define it, then it should result in a linker error.Konrad Rudolph : Richard, you're mixing intialization with definition here; you can well initialize the constant inline and define it separately. But you're right, I need to clarify my posting.From Konrad Rudolph -
Well, if needed, you can take the address of a static const Member Value. You've have to declare a separate member variable of enum type to take the address of it.
From James Curran -
One difference is that the enum defines a type that can be used as a method parameter, for example, to get better type checking. Both are treated as compile time constants by the compiler, so they should generate identical code.
From Mark Ransom -
Another third solution?
One subtle difference is that the enum must be defined in the header, and visible for all. When you are avoiding dependencies, this is a pain. For example, in a PImpl, adding an enum is somewhat counter-productive:
// MyPImpl.hpp class MyImpl ; class MyPimpl { public : enum { Life = 42 } ; private : MyImpl * myImpl ; }Another third solution would be a variation on the "const static" alternative proposed in the question: Declaring the variable in the header, but defining it in the source:
// MyPImpl.hpp class MyImpl ; class MyPimpl { public : static const int Life ; private : MyImpl * myImpl ; }.
// MyPImpl.cpp const int MyPImpl::Life = 42 ;Note that the value of MyPImpl::Life is hidden from the user of MyPImpl (who includes MyPImpl.hpp).
This will enable the MyPimpl author to change the value of "Life" as needed, without needing the MyPImpl user to recompile, as is the overall aim of the PImpl.
From paercebal -
static const values are treated as r-values just like enum in 99% of code you'll see. That means there is never memory generated for them. The only advantage to using enums is they can't become l-values in that other 1%. The
static constvalue has the advantage that it is type safe. You can also havestatic const floatvalues.The compiler will make
Foo::Lifean l-value if it has memory associated with it. The primary way to do that is to take it's address. e.g.&Foo::Life;Using gcc there is another more subtle way!
int foo = rand()? Foo::Life: Foo::Everthing;The compiler generated code uses the addresses of Life and Everthing. It compiles fine but will give a linker error about the missing addresses for
Foo::LifeandFoo::Everthing. This behavior is completely standard conforming, though obviously undesirable.Note, neither of these statements will generate the linker error.
int foo = rand()? Foo::Life: Bar::Life; int bar = rand()? Foo::Life: Foo::Everthing + 0;Once you have a compiler conforming to the C++0x the correct code will be
class Foo { public: constexpr size_t Life = 42; };This is guaranteed to always be an l-value, the best of both worlds.
From caspin
0 comments:
Post a Comment