views:

602

answers:

3

The code I'm working with has its own smart pointer implementation which does simple reference counting. Yes, we shouldn't have our own implementation. Yes, we should be using one from boost or some such. Bear with me.

I found I wanted to write code like this:

...
CountedPointer<Base> base;
...
CountedPointer<Derived> derived;
...
base = derived;

However, the copy constructor for CountedPointer has a prototype like this:

CountedPointer(const CountedPointer<T> &other);

So the above code won't compile since it can't find a suitable constructor (or assignment operator - it's the same story there). I tried re-writing the copy constructor with a prototype like this:

template<U>
CountedPointer(const CountedPointer<U> &other);

However, I hit the problem that the copy constructor must access a private member of the object it's copying (i.e. the raw pointer) and if it's in a different specialisation of CountedPointer, they're not visible.

Alexandrescu avoids this problem in his library Loki by having accessor functions for the encapsulated pointer, but I'd prefer not to give direct access to the raw pointer if possible.

Is there any way I can write this to allow the derived to base copy, but not allow general access to the raw pointer?

Update: I've implemented the accepted answer below, and it works well. I spent a while figuring out why my program seg-faulted horribly when I only provided the templated version of the copy constructor, replacing the original un-templated version. Eventually, I realised that the compiler doesn't regard the templated version as being a copy constructor, and provides a default one. The default one just dumbly copies the contents without updating the counter, so I end up with dangling pointers and double frees. The same sort of thing applies to the assignment operator.

+3  A: 

"Alexandrescu avoids this problem in his library Loki by having accessor functions for the encapsulated pointer, but I'd prefer not to give direct access to the raw pointer if possible"

I think the cost of adding a raw pointer getter is going to be much less than the complexity cost of trying to get around not having raw access. There just isn't a language mechanism to convert instances between two unrelated template classes. To the compiler they are two completly different things with no relationship at run time. That's why you one template class instance can't access the others privates.

You could consider creating such a relationship with a base class for all CountedPointers. You might have to put a void* in this base class. Then you'd have to do all the checking yourself (is T derrived from U then force the cast... Can I implicitly convert a T to a U?, if so force a conversion.. etc) but that may get pretty complex. Here's a rough start for this approach:

class CountedPointerBase
{
    void* rawPtr;
};

template <class T>
class CountedPointer : public ConutedPointerBase
{
      T* myRawT = reinterpret_cast<T*>(rawPtr);

      template<class U>
      CountedPointer( CountedPointer<U> u)
      {
           // Copying a CountedPointer<T> -> CountedPointer<U>
           if (dynamic_cast<U*>(myRawT) != NULL)
           {
               // Safe to copy one rawPtr to another
           }
           // check for all other conversions
      }
}

There may be a lot of other complexities in seeing if two types are compatible. Maybe there's some Loki/Boost template slickness that can determine for two type arguments if you could cast one to another.

Anyway, as you can see, this might be a much more complex solution then just adding a getter. Being able to get the raw pointer has other benefits also. You can pass raw pointers into library functions that only accept raw pointers, and use them as temporaries, for example. It can be dangerous, I suppose, if someone on your team decides to hold onto a copy of the raw pointer as opposed to the smart ptr. That person should probably be summarily trout slapped.

Doug T.
+3  A: 

Can't you make them friends? Like:

template<typename T>
class CountedPointer {

    // ...

    template<U>
    CountedPointer(const CountedPointer<U> &other);

    template<typename U> friend class CountedPointer;
};
mitchnull
That does the trick! I wonder if you could add some details about what's going on here? For example, I'm not exactly sure why the friend declaration is templated.
Ned
I'm not sure how the U in friend template declaration is related to the U in the copy constructor template declaration
Doug T.
@Doug T. the U in the friend is kinda unrelated to the U in the copy ctor. I could've used V for the friend, and it'd work the same.
mitchnull
@Ned the friend declaration must be templated because the class is templated. If the friend were not templated, it would make the same type a friend, which is kinda pointless :) You need to use a different type (not T) in the friend declaration, otherwise it would shadow the "main" template param
mitchnull
Ah so you are making all classes based off of CountedPointer template friends.
Doug T.
A: 

You will solve the problem if you have a CountedPointer(T* other); constructor and a similar assignment operator.

sharptooth
Wouldn't that imply that a CountedPointer<U> is implicitly convertable to a T*? That's the crux of his problem, they really aren't....
Doug T.
Yeap, that's implied. I see no problem declaring an operator T* than will return the wrapped pointer.
sharptooth
I'd rather not give anyone access to the raw pointer. What if they delete it? Or keep it beyond the life-time of the CounterPointer? Allowing the implicit conversion also permits "delete cp;" to compile for a CountedPointer cp - and it will leave a dangling pointer in cp.
Ned
Well, if you decide to use reference counting, you better not use delete and not allocate objects with reference counting on stack. That's common sense.You don't call delete on objects created by CoCreateInstance, do you?
sharptooth