Re: Design Question Pt-1: Forcing const pointers and reference counting in graph structure
On 13 Oct, 19:43, Ismail Pazarbasi <pazarb...@gmail.com> wrote:
Hi,
I am trying to design a compact (scene) graph library. It has nothing
to do with actual rendering of a scene whatsoever but strictly dealing
with logical relationship between objects.
I didn't think about it thoroughly, and as a matter of fact there is
no real code at the moment. I didn't want to begin writing code before
thinking enough about it. If there is a fundamental flaw in the idea/
design, then there is no point to waste my time.
Traversing graph is the most important part and I consider using
parallel algorithms. To ease this operation, I thought about returning
a const pointer after instantiating objects. I thought this approach
would enforce a stricter policy for mutating objects, and increase
chance of dealing with read-only objects, make it easy to detect
races. To access mutator, caller should use MutableObject<T> for
"attaching" to the object to mutate.
The first problem I thought about was reference counting. All objects
are reference counted. Enough with long story, here is what I thought
(only theory, never compiled):
// reference counter, top in inheritance hierarchy
class RefCounted
{
volatile long m_cref;
public:
long AddRef() { return atomic_increment(&m_cref); }
long Release()
{
long cref = atomic_decrement(&m_cref);
if (0 == cref) delete this;
return cref;
}
protected:
virtual ~RefCounted() { }
};
// factory that instantiates object and return const pointer
template<typename T>
struct ObjectFactory
{
static const T* Create()
{
return new T;
}
};
// to prevent use of operator new directly
class ConstObject
{
template<typename T> friend struct ObjectFactory<T>;
protected:
void* operator new(size_t);
void* operator new[](size_t);
void operator delete(void*);
// ... other overloads
};
// generic object class; holds basic information about an object
class Object : public RefCounted, public ConstObject
{
template<typename T>
friend class MutableObject<T>;
void AttachMutator(); // some kind of mutex?
void DetachMutator(); // unlocking the mutex?
public:
const std::string& GetName() const;
void SetName(const std::string&);
};
// idea stolen from Microsoft's CComPtr
template<typename T>
class NoAddRefRelease : public T
{
private:
long AddRef();
long Release();
};
// RefObj is intrusive smart pointer
template<typename T>
class RefObj
{
T* m_p;
public:
explicit RefObj(const T* pT = NULL)
: m_p(const_cast<T*>(pT)
{
if (m_p) m_p->AddRef();
}
NoAddRefRelease<T>* operator->() { return (NoAddRefRelease<T>*>)
m_p; } // see below note
NoAddRefRelease<T>* get() const { return (NoAddRefRelease<T>*)m_p; }
void reset(T* pT = NULL)
{
if (m_p == pT) return;
if (m_p) m_p->Release();
if (pT) pT->AddRef();
m_p = pT;
}
};
// a node in graph
class Node : public Object
{
protected:
~Node();
};
To deal with RefObj<T> and reference counting, I thought may be
ObjectFactory<T>::Create returns a RefObj<T> directly, and, RefObj's
operator->() and get() returns a const NoAddRefRelease<T>*.
I would like to achieve following:
Node* pNode = new Node; // error
RefObj<Node> spNode = ObjectFactory<T>::Create();
spNode->SetName("myName"); // error
spNode.get()->SetName("myName"); // error
delete spNode.get(); // error
MutableObject<Node> mutNode(spNode);
mutNode->SetName("myName"); // ok
Therefore, MutableObject may look like:
template<typename T>
class MutableObject
{
T* m_p;
public:
explicit MutableObject(RefObj<T>& rObj)
: m_p(rObj.get())
{
m_p->AttachMutator(this);
}
~MutableObject() { m_p->DetachMutable(); }
NoAddRefRelease<T>* operator->() { return m_p; }
};
This requires changes in RefObj<T> to allow access from
MutableObject<T> to its m_p or a private get() method which will
return a non-const qualified T*.
This is entirely on the paper, may be ridiculous, and I am not sure
how practical it is, and that's why I wanted to bring this up here for
discussion.
I'd be very happy, if you share your valuable feedback.
Regards,
Ismail
It looks excessively complicated and confused to me.
For reference counting use shared_ptr (see boost or your compiler).
I don't understand what you are trying to do with MutableObject. It
seems to me that you are trying to violate const correctness.
The "normal" way to deal with graphs and const/mutable is to have a
graph class and work primarily through that rather than through the
nodes themselves - that way you don't have to convert a const node to
a mutable one.
The graph class also removes any need for a factory which is, in any
case, logically dubious - What does it mean to have a node that isn't
in a graph? and hence any desire for unnecessary clutter to prevent
allocating a node. The only downside is that you can't then have
polymorphic nodes but you don't seem to want them anyway.
If you do want to access a Node* from a const Node* then they way to
do it is to get the non const graph g to do it for you:
Graph g;
const Node* const_n;
....
Node* n = g.Find(const_n);
If the node contains a Graph* then
Node* Graph::Find(const Node* n) // graph is not const
{
if( n->Graph() != this ) throw "a tantrum";
return const_cast<Node*>(n);
}
You also seem to be concerned about concurrent access which is not
usually possible in the most general case for a graph so, again,
either it is best to work through a graph object as this can easily
provide thread safe operations or you probably want to go for some
sort of subtree locking (because it eliminates a lot of deadlock
problems) class using RAII:
{
subtree_lock l(node);
// subtree is locked
}
// subtree is unlocked
Hope this helps
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]