20

I have a quick question regarding the use of typedefs for lengthy templates. The crux: I've found myself in something of a pickle—there doesn't seem to be a good place to place typedefs except local to client functions. While there are similar SO questions (see here for example), none seem to address this exactly. Please note that this question doesn't address whether typedefs are desirable in what follows—I've tried to simplify things for expository purposes.

My problem has arisen while working with boost::shared_ptr<T>. Basically, I want to do the following:

#include <boost/shared_ptr.hpp>
typedef boost::shared_ptr<Widget> WidgetPtr;

Placing this typedef in the Widget declaration header seems ugly. There seem to be two considerations here: (i) if Widget itself doesn't make use of shared pointers in its members, we've added an additional include (as we can't forward declare the boost::shared_ptr template class—correct me if I'm wrong?) (ii) if we want to make use of this typedef during the declaration of another class (call that class Foo) we violate best practices by including Widget.h instead of simply forward declaring Widget or including WidgetFwd.h... unless this typedef is duplicated in the latter. Furthermore, it doesn't seem make sense to typedef boost::shared_ptr<Widget> during the declaration of Widget itself—we seem to be mixing Widget's declaration with an anticipation of how clients will make use of the Widget interface.

Okay, so that's bad, but this is worse: if I don't attempt some combination of the above I end up with duplicate typedefs in client code, which yields inconsistency (and hence, likely, error)—the whole point being that given Widget, a WidgetPtr typedef should act as a type in its own right. Example: we don't want Foo to make use of one WidgetPtr, a typedef of boost::shared_ptr, while Bar is using WidgetPtr as a typedef for std::auto_ptr.

Another method (and one of the few that I've seen mentioned in online discussion) would be to make the typedef a public member of Widget and then use Widget::Ptr:

class Widget {
// ...
public:
     typedef boost::shared_ptr<Widget> Ptr;
};

Again, I don't like this as (i) it suggests that the pointer type is somehow a member of the class and (ii) it leads to a wonky interface. Worse still: since every class that I write can potentially be pointed to using smart pointers, I end up chasing the imaginary client's tail. Ugly, ugly, ugly.

As it stands, I've removed the typedefs from this codebase (as they led to serious confusion, duplication) and re-introduced them locally in selected functions. Here again there's a problem with inconsistent usage but it's not quite as severe.

The only other solution I can think of—and again I'm not sure whether this is considered good practice—would be to have a utilities header in which the typedefs are placed, potentially within their own namespace. In this header we'd include and be done with it.

Am I missing something obvious or is this just plain tricky?

PS—Apologies for the length of the above; I couldn't find a simpler way of fully expressing the problem.

Community
  • 1
  • 1
Marc
  • 233
  • 2
  • 7
  • 2
    BTW- what's the rationale behing typedefing this, in the first place? The name `shared_ptr` is very straightforward- a shared pointer to a widget. No obvious code bloat here (except the `boost::` part) imho... – Kos Dec 13 '10 at 13:54
  • @Kos: I think the problem arises if you have something like Widget instead of just Widget. As soon as the type doesn't fit the screen, you start about typedef'ing :-) – Philipp Dec 13 '10 at 13:59
  • If you wan't to change shared pointers in future, typedefing is quite handy. – Pawel Zubrycki Dec 13 '10 at 14:01
  • 1
    As I said, I've simplified things considerably. The code that led to this question makes use of `std::map`s that have shared pointers for keys. Given the verbosity, I started thinking about whether `typedef`ining would help and ran into this problem. – Marc Dec 13 '10 at 14:06
  • @Philipp - then I'd prefer to typedef the type itself and use `shared_ptr` instead of `TypedefedSharedPtrToTemplateInstance`. :) I find this analogous to NOT using typedefs like `FooPtr` in place of `Foo*`. – Kos Dec 13 '10 at 14:06
  • 1
    @Marc - Each `std::map` template instance has the typedefs `key_type` and `value_type` for that. :) – Kos Dec 13 '10 at 14:08
  • @Kos Neat, thanks for the tip! – Marc Dec 13 '10 at 14:13

7 Answers7

7

Furthermore, it doesn't seem make sense to typedef boost::shared_ptr during the declaration of Widget itself—we seem to be mixing Widget's declaration with an anticipation of how clients will make use of the Widget interface.

First of all, this is not at all wrong - after all, the means of how the clients will (and can) use the interface is part of the interface itself; and for C++, not being garbage-collected, memory management of objects is a rather crucial part of their interface.

So there are two cases. In one case, the Widget would anticipate it would be used through a shared pointer. This would mean that eg. child widgets obtained from a widget are returned as shared_ptrs, everywidget created has it shared_ptr and so on. It would be totally legitimate to typedef WidgetPtr in the same header as Widget.

In the second case, Widgets would expect to be managed eg. by ordinary new and delete. The clients can use shared_ptrs in special cases, but nothing says eg. a printer dialogue routine can't use auto_ptr instead. The clients have to be prepared that if wptr is a shared_ptr, the line

shared_ptr<Widget> w2(wptr->firstChild()->parent());

leads to a disaster.

Your question seems to indicate the latter is your case. So IMHO, what you've done is OK. The clients can choose their means of managing Widget objects, as long as it doesn't affect other clients.

jpalecek
  • 47,058
  • 7
  • 102
  • 144
  • Yes, my sketch very much suggested that the latter is the case; thanks for the feedback. – Marc Dec 13 '10 at 14:26
6

I don't like a library dictating the use of a particular smart pointer, but I tolerate it if it is necessary.

If you wish to force users to always use a shared_ptr to manipulate a widget, it's impossible, so don't even bother trying.

On the other hand, if you have a method from Widget which returns a boost::shared_ptr<Widget>, then providing a (sane) typedef might simplify the client code.

I would therefore promote the use of an inner typedef:

class Widget
{
public:
  typedef boost::shared_ptr<Widget> Ptr;

  Ptr AccessFirstChild();
}; // class Widget

in which case it's perfectly okay to #include the necessary headers.

Matthieu M.
  • 287,565
  • 48
  • 449
  • 722
  • 1
    I would advocate this too, and especially if `Widget` is templated, I can't see any of the other approaches working correctly (i.e. typedef in `Widget.h` - outside of the class - for example would assume that you know every possible *type* of `Widget`) – Nim Dec 13 '10 at 15:13
  • I guess I didn't really address this case in my original question—I like @Nim's point about preferring this approach for templatic types. – Marc Dec 13 '10 at 15:20
  • 1
    So it would seem we have (loosely) three cases: (i) typedef for convenience (`Widget` doesn't make use of any `shared_ptr` instances among its members)—perhaps best done by the client; (ii) typedef for a templated `Widget`—here `Widget::Ptr` makes a good deal of sense; (iii) typedef for convenience of defining members—here it also makes sense to place the typedef in the `Widget` header as `WidgetPtr` is already part of the interface; if `Widget` isn't generic, we could typedef outside of the class declaration. Fair summary? – Marc Dec 13 '10 at 15:24
  • @Marc: I don't understand how the 3rd point is different from the 1st, sorry :/ Could you illustrate what you mean by `convenience of defining member` ? – Matthieu M. Dec 13 '10 at 15:45
  • Sorry, it's all a little condensed. In the case of (i), `Widget` doesn't have any members that instantiate `shared_ptr` whereas in (iii) some members do require `shared_ptr` instantiation (consider a factory pattern, for example). In (i), "convenience" refers to simplifying client code, whereas in (iii) it refers to simplifying member declarations. – Marc Dec 13 '10 at 15:52
  • 1
    @Marc: if it's for member declaration I would go for typedef'ing within the class (as I did in my code), in order not to "pollute" the namespace. Since if you need access to the member function, then you need access to the class definition and forward declaration is therefore useless. – Matthieu M. Dec 13 '10 at 16:03
  • The remaining case, (i), still leaves us open to the problem outlined in the original question: `WidgetPtr` could being defined in mutually exclusive ways (i.e., `WidgetPtr` could be a token representing different types). As a result of our discussion, I would be inclined to move away from the header+namespace approach as this somewhat obscures the underlying problem: `WidgetPtr` isn't really a well-defined _concept_ to begin with. Given that members don't employ it, we're really just providing a shorthand for client code. As such, I'd be inclined to `typedef` as locally as possible. Thoughts? – Marc Dec 13 '10 at 16:18
  • 1
    @Marc: yes I agree, let's each client `typedef` it in as tight as possible scopes. This way there are free to change the name or the representation freely and there won't be any clash. – Matthieu M. Dec 13 '10 at 16:30
5

You are overthinking this in my opinion. Everybody who wants to have a shared_ptr<Widget> is going to have to include the Widget header file anyway. Putting the typedef (which is a good idea imo) in Widget.h makes 100% sense to me.

Steve Townsend
  • 53,498
  • 9
  • 91
  • 140
  • Maybe I am—but this solution involves the implementer of the _interface_ guessing how clients of the _implementation_ will want to point to a `Widget`. – Marc Dec 13 '10 at 14:10
  • @Marc - the use of `shared_ptr` is typically driven by the semantics of the class. The 'normal' case would just be to use object copies. I would therefore argue that saying that a `Widget` is something optimally managed using `shared_ptr` should be part of the interface. That does not prevent alternative usage of course. – Steve Townsend Dec 13 '10 at 14:18
  • Interesting. Would you agree that placing the `typedef` in `Widget.h` for _convenience alone_ (i.e., in a situation where `Widget` has no special semantics) would not be warranted then? – Marc Dec 13 '10 at 14:24
  • @Marc - to me that would make sense, in that case the client(s) could drive the `typedef` location if needed. I don't view having multiple typedefs in this scenario is a catastrophe, but if you really wanted you could centralize them. – Steve Townsend Dec 13 '10 at 14:28
  • I suppose in the event of multiple typedefs due to the lack of special semantics, clients should be responsible for localising the definitions in either function scope or an anonymous namespace. I do still worry about the implications of having a `WidgetPtr` type that can vary depending on both scope and compilation module—seems messy to me. – Marc Dec 13 '10 at 14:32
  • Incidentally, no one's addressed the idea of a utility header/namespace yet; would that be better or worse than the other proposals outlined so far? – Marc Dec 13 '10 at 14:35
  • 5
    @Steve Townsend: a simple forward declaration of `Widget` is enough to manipulate `shared_ptr` of Widgets, as long as you do not try to access methods of `Widget`, evidently. – Matthieu M. Dec 13 '10 at 14:59
3

My approach (using underbar types, just because it is how I do it)

class Type
{
    public:
        typedef shared_ptr<Type>        ptr;
        typedef shared_ptr<const Type>  const_ptr;
};

I have found the const_ptr version is pretty darn useful.

Cort Ammon
  • 10,221
  • 31
  • 45
  • That is exactly what I am considering at the moment, since you often need smart pointers to constant instances **and** to mutable instances. – Florian Wolters Oct 21 '14 at 13:21
1

I used to structure my C++ code into libraries. A library would have a bunch of headers for client consumption, all inside the include/LibraryName directory. Also, I would have one header called Fwd.h inside this directory with forward declarations of all classes along with their pointer typedefs.

In addition, each public header would include Fwd.h so that including the header would automatically give you all forward declarations and pointer typedefs. This worked really well in practice.

Not all classes are necessary to place in a shared_ptr though. I would only create pointer typedefs for types that I expected to be created dynamically, and in this case I would supply a factory. This has the added benefit that you may get away with supplying client code with interface types only, and hide concreted implementations in your library's src directory. Not specifically what you asked advice for, but this gives the complete picture of my method. As a final point, it's also possible to supply a convenience header called LibraryName.h that includes the Fwd.h and all other public headers.

Good luck!

Daniel Lidström
  • 9,930
  • 1
  • 27
  • 35
1

I generally use this approach to ease typing and makes a generic shared pointer interface for classes. Do note it's C++0x.

#include <iostream>
#include <memory>

template <class T>
struct SharedVirtual
{
   typedef std::shared_ptr<T> VPtr;
};

template <class T>
struct Shared
{
   typedef std::shared_ptr<T> Ptr;

   template <class... P>
   static Ptr ptr(P&&... args) 
   { 
      return std::make_shared<T>(std::forward<P>(args)...); 
   }
};

class Foo : public SharedVirtual<Foo>
{
   public:
      virtual void foo() const = 0;
};

class Test : public Foo, public Shared<Test>
{
   public:
      void foo() const { std::cout << "Hai u!" << std::endl; }
};

void print(const Foo::VPtr& ptr)
{
   ptr->foo();
}

int main()
{
   auto ptr = Test::ptr();
   print(ptr);
}
Maister
  • 4,978
  • 1
  • 31
  • 34
0

Second part first: use a namespace, i.e.:

namespace WidgetStuff {
  class Widget { ..
  typedef shared_ptr<Widget> WidgetPtr;
  ..

If you want to split it up:

namespace WidgetStuff {
   class Widget { ...
}
...
namespace WidgetStuff { 
  typedef ...

You're the library author, you own the namespace, so no one else should invade it.

And now part one is answered too, if you choose you can do:

#include <widget.h>
#include <widget_utils.h>

by splitting up the namespace as above. The effect is no one has to use the utilities, whether or not they do they should not invade your namespace, so they're free to make WidgetPtr mean something else, as long as it isn't in your namespace.

Yttrill
  • 4,725
  • 1
  • 20
  • 29