views:

1604

answers:

8

Something I have found myself doing often lately is declaring typedefs relevant to a particular class inside that class, i.e.

class Lorem
{
    typedef boost::shared_ptr<Lorem> ptr;
    typedef std::vector<Lorem::ptr>  vector;

//
// ...
//
};

These types are then used elsewhere in the code:

Lorem::vector lorems;
Lorem::ptr    lorem( new Lorem() );

lorems.push_back( lorem );

Reasons I like it:

  • It reduces the noise introduced by the class templates, std::vector<Lorem> becomes Lorem::vector, etc.
  • It serves as a statement of intent - in the example above, the Lorem class is intended to be reference counted via boost::shared_ptr and stored in a vector.
  • It allows the implementation to change - i.e. if Lorem needed to be changed to be intrusively reference counted (via boost::intrusive_ptr) at a later stage then this would have minimal impact to the code.
  • I think it looks 'prettier' and is arguably easier to read.

Reasons I don't like it:

  • There are sometimes issues with dependencies - if you want to embed, say, a Lorem::vector within another class but only need (or want) to forward declare Lorem (as opposed to introducing a dependency on its header file) then you end up having to use the explicit types (e.g. boost::shared_ptr<Lorem> rather than Lorem::ptr), which is a little inconsistent.
  • It may not be very common, and hence harder to understand?

I try to be objective with my coding style, so it would be good to get some other opinions on it so I can dissect my thinking a little bit.

A: 

I recommend to move those typedefs outside the class. This way, you remove direct dependency on shared pointer and vector classes and you can include them only when needed. Unless you are using those types in your class implementation, I consider they shouldn't be inner typedefs.

The reasons you like it are still matched, since they are solved by the type aliasing through typedef, not by declaring them inside your class.

Cătălin Pitiș
That would polute the anonymous namespace with the typedefs wouldn't it?! The problem with typedef is that it hides the actual type, which can cause conflicts when included in/by multiple modules, which are hard to find/fix. It's a good practice to contain these in namespaces or inside classes.
Indeera
Name conflicts and anonymous namespace polution has little to do with keeping a typename inside a class or outside. You can have a name conflict with your class, not with your typedefs. So in order to avoid name polution, use namespaces. Declare your class and the related typedefs in a namespace.
Cătălin Pitiș
Another argument for putting the typedef inside a class is the use of templatised functions. When, say, a function receives an unknown container type (vector or list) containing an unknown string type (string or your own string-conformant variant). the only way to figure out the type of the container payload is with the typedef 'value_type' which is part of the container class definition.
Marius
+21  A: 

I think it is excellent style, and I use it myself. It is always best to limit the scope of names as much as possible, and use of classes is the best way to do this in C++. For example, the C++ Standard library makes heavy use of typedefs within classes.

anon
That's a good point, I wonder it looking 'prettier' was my subconscious delicately pointing out that limited scope is a *good* thing. I wonder though, does the fact that the STL uses it predominately in class templates make it a subtly different usage? Is it harder to justify in a 'concrete' class?
Will Baker
Well the standard library is made up of templates rather than classes, but I think the justification is the same for both.
anon
A: 

When the typedef is used only within the class itself (i.e. is declared as private) I think its a good idea. However, for exactly the reasons you give, I would not use it if the typedef's need to be known outside the class. In that case I recommend to move them outside the class.

Stefan Rådström
+3  A: 

Typdefs are definitely are good style. And all your "reasons I like" are good and correct.

About problems you have with that. Well, forward declaration is not a holy grail. You can simply design your code to avoid multi level dependencies.

You can move typedef outside the class but Class::ptr is so much prettier then ClassPtr that I don't do this. It is like with namespaces as for me - things stay connected within the scope.

Sometimes I did

Trait<Loren>::ptr
Trait<Loren>::collection
Trait<Loren>::map

And it can be default for all domain classes and with some specialization for certain ones.

Mykola Golubyev
+3  A: 

The STL does this type of thing all the time - the typedefs are part of the interface for many classes in the STL.

reference
iterator
size_type
value_type
etc...

are all typedefs that are part of the interface for various STL template classes.

Michael Burr
True, and I suspect this is where I first picked it up. It seems like these would be a little easier to justify though? I can't help but view typedefs within a class template as being more akin to variables, if you happen to think along the 'meta-programming' line.
Will Baker
+4  A: 

Typedefs are the ones what policy based design and traits built upon in C++, so The power of Generic Programming in C++ stems from typedefs themselves.

Comptrol
+1  A: 

Another vote for this being a good idea. I started doing this when writing a simulation that had to be efficient, both in time and space. All of the value types had an Ptr typedef that started out as a boost shared pointer. I then did some profiling and changed some of them to a boost intrusive pointer without having to change any of the code where these objects were used.

Note that this only works when you know where the classes are going to be used, and that all the uses have the same requirements. I wouldn't use this in library code, for example, because you can't know when writing the library the context in which it will be used.

KeithB
A: 

It serves as a statement of intent - in the example above, the Lorem class is intended to be reference counted via boost::shared_ptr and stored in a vector.

This is exactly what it does not do.

If I see 'Foo::Ptr' in the code, I have absolutely no idea whether it's a shared_ptr or a Foo* (STL has ::pointer typedefs that are T*, remember) or whatever. Esp. if it's a shared pointer, I don't provide a typedef at all, but keep the shared_ptr use explicitly in the code.

Actually, I hardly ever use typedefs outside Template Metaprogramming.