views:

1986

answers:

6

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?

+9  A: 

They're not identical:

size_t *pLife1 = &Foo::Life;
size_t *pLife2 = &Bar::Life;
Essentially what I said, in less words. ;-)
Konrad Rudolph
You're fast, partner. But I'll always be the fastest gun in these here parts...
See, that's why I like Stack Overflow...
John Dibling
Mike F, I assume that the scond line is supposed to generate a compile error. Perhaps you could add a comment stating that.
jwfearn
yes, and you should change it to size_t const* pLife1
Johannes Schaub - litb
+16  A: 

The enum hack 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 const variant is if you want to forbid taking the address of the value: you can't take an address of an enum value 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.
Konrad Rudolph
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.
Richard Corden
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.
Konrad Rudolph
+4  A: 

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.

James Curran
+2  A: 

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.

Mark Ransom
A: 

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.

paercebal
+1  A: 

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 const value has the advantage that it is type safe. You can also have static const float values.

The compiler will make Foo::Life an 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::Life and Foo::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.

caspin