Re: Smart pointers and inclomplete types
Marcel M?ller wrote:
I have a problem with a cyclic dependency of two classes:
class Iref_count // Interface for the intrusive_ptr
{ friend class int_ptr_base; // for access to Count
private:
volatile unsigned Count;
protected:
Iref_count() : Count(0) {}
// You must not call the non-virtual destructor directly.
~Iref_count() {}
};
class Slice;
class Iterator
{ intrusive_ptr<Slice> root;
// some more stuff
};
class Slice : public Iref_count
{ scopec_ptr<Iterator> start;
scopec_ptr<Iterator> stop;
// some more stuff
};
To get this to compile either scoped_ptr or intrusive_ptr have to accept
an incomplete type.
Unfortunately I did not have luck with this so far. Neither Slice nor
Iterator are PODs. And the forward declaration of Slice generates wrong
code in intrusive_ptr at the conversion from Iref_count* to Slice*. The
pointer value is not translated by the offset of Iref_count.
Note that intrusive_ptr is not boost::intrusive_ptr, because that won't
compile on my platform. I wrote something similar (see below). However,
I am unsure whether boost::intrusive_ptr would have defined behavior in
this case, and if it does so, why?
It would not.
However, std::tr1::shared_ptr (and boost::shared_ptr) probably would.
Any Ideas?
The cyclic dependency is really needed from the designs point of view.
You might want to have a look at the implementation of boost::shared_ptr.
The key idea is to store a deleter within the shared_ptr and initialize
that one upon construction (with an appropriate default). This way, the
problem can be postponed until shared_ptr objects need to be initialized.
Only at that point, the type has to be complete.
/* Abstract non-template base class of int_ptr */
class int_ptr_base
{protected:
Iref_Count* Ptr;
public:
// Store a new object under reference count control
// or initialize a NULL pointer.
int_ptr_base(Iref_Count* ptr);
// Copy constructor
int_ptr_base(const int_ptr_base& r);
// Destructor core
Iref_Count* unassign();
// some more functions...
};
template <class T>
class int_ptr : protected int_ptr_base
{public:
// Store a new object under reference count control
// or initialize a NULL pointer.
int_ptr(T* ptr = NULL) : int_ptr_base(ptr) {}
// Destructor, frees the stored object if this is the last reference.
~int_ptr() { delete (T*)unassign(); }
The line above is either wrong or too smart: nothing (except the comment)
indicates that the reference has to be last.
// Basic operators
T* get() const { return (T*)Ptr; }
T& operator*() const { assert(Ptr); return *(T*)Ptr; }
T* operator->() const { assert(Ptr); return (T*)Ptr; }
// some more functions...
};
int_ptr_base::int_ptr_base(Iref_Count* ptr)
: Ptr(ptr)
{ if (Ptr)
++Ptr->Count; // normally InterlockedInc(Ptr->Count);
}
int_ptr_base::int_ptr_base(const int_ptr_base& r)
: Ptr(r.Ptr)
{ if (Ptr)
++Ptr->Count; // normally InterlockedInc(Ptr->Count);
}
Iref_Count* int_ptr_base::unassign()
{ return Ptr && --Ptr->Count == 0 ? Ptr : NULL; // normally
InterlockedDec(Ptr->Count)
}
Best
Kai-Uwe Bux