Smart pointers and inclomplete types

M

Marcel Müller

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?


Any Ideas?
The cyclic dependency is really needed from the designs point of view.


/* 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(); }

// 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)
}
 
K

Kai-Uwe Bux

Marcel said:
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
 
M

Marcel Müller

Kai-Uwe Bux said:
It would not.

However, std::tr1::shared_ptr (and boost::shared_ptr) probably would.


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.

Hmm, I intensionally preferred intrusive pointers because of their small
memory footprint. Furthermore it is a major design change, because the
current interfaces rely on the fact that passing T* instead of
int_ptr<T> as function argument is sufficient even if int_ptr<T>
instances may be assigned from the parameter in the function body.

Maybe I can apply something like that what you have mentioned to the
scoped_ptr and forward declare the Iterator class.

The line above is either wrong or too smart: nothing (except the comment)
indicates that the reference has to be last.

unassign returns NULL unless it removes the last reference. Well, not
documented that nicely. The whole trick is to do anything but the
absolutely needed part in a non-template base. This keeps the binary
compact.


Marcel
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,955
Messages
2,570,117
Members
46,705
Latest member
v_darius

Latest Threads

Top