4
\$\begingroup\$

I am toying with a simple class to inherit from to manage my dynamic resources. I am fairly new to C++, so my "implementation" might be suboptimal. Suggestions how to improve functionality are welcome. There are plenty of couts for orientation purposes, so feel free to ignore those.

The idea of UObject is to organize objects in a parent hierarchy so that when an element leaves the scope he picks up all dynamic resources without having to worry about forgetting to type delete and get a memory leak.

The UObject is minimalistic; all it will feature in its complete state will be a pointer to a container of children. The std::string name is too just for orientation and will later on be dropped. When a UObject is created, its resource pointer is set to 0. Every time another object is parented, the dynamic resource is created and children put there. When the UObject "dies" it checks whether is has children, and if so, kills them all one by one. The children kill their own children and so on every dynamic resource if picked up along the way.

Header:

class UObject
{
public:
    UObject(UObject *parent, const std::string &xname);
    ~UObject(); 
    std::list<UObject*> *children;

private:
    void adopt(UObject *child);
    void createRes();
    std::string name;
};

Implementation:

UObject::UObject( UObject *parent /*= 0*/, const std::string &xname) : name(xname)
{
    std::cout << "Creating " << name << " Looking for parent... ";
    if (parent)
    {
        parent->adopt(this);
    }
    else
    {
        std::cout << "no parent ";
    }
    children = NULL;
    std::cout << name << " created \n" << std::endl;
}

UObject::~UObject()
{
    if (children)
    {
        std::cout << "Children found, deleting... \n";
        while(!children->empty())
        {
            delete children->front();
            children->pop_front();
            std::cout << "child deleted" << std::endl;
        }
        delete children;
        std::cout << name << " Resource deleted" << std::endl;
    }
    else
    {
        std::cout << "No children to delete ";
    }
    std::cout << name << " has been deleted \n" << std::endl;   
}

void UObject::adopt( UObject *child )
{
    if (!children)
    {
        createRes();
    }
    children->push_back(child); 
    std::cout << "Child adopted by " << name << std::endl;
}

void UObject::createRes()
{
    children = new std::list<UObject*>;
    std::cout << "Resource created \n";
}

A simple test:

int main()
{
    UObject obj1(0, "GRANDFATHER");
    UObject *pobj2 = new UObject(&obj1, "SON1");
    UObject *pobj3 = new UObject(&obj1, "DAUGHTER1");
    UObject *pobj4 = new UObject(&obj1, "DAUGHTER2");
    UObject *pobj5 = new UObject(pobj2, "GRANDSON1");
    UObject *pobj6 = new UObject(pobj3, "GRANDSON2");
    UObject *pobj7 = new UObject(pobj3, "GRANDSON3");
    UObject *pobj8 = new UObject(pobj2, "GRANDDAUGHTER1");
    UObject *pobj9 = new UObject(pobj3, "GRANDDAUGHTER2");
}

And the console output to indicate all dynamic resources are gone without a single delete:

Creating GRANDFATHER Looking for parent... no parent GRANDFATHER created

Creating SON1 Looking for parent... Resource created
Child adopted by GRANDFATHER
SON1 created

Creating DAUGHTER1 Looking for parent... Child adopted by GRANDFATHER
DAUGHTER1 created

Creating DAUGHTER2 Looking for parent... Child adopted by GRANDFATHER
DAUGHTER2 created

Creating GRANDSON1 Looking for parent... Resource created
Child adopted by SON1
GRANDSON1 created

Creating GRANDSON2 Looking for parent... Resource created
Child adopted by DAUGHTER1
GRANDSON2 created

Creating GRANDSON3 Looking for parent... Child adopted by DAUGHTER1
GRANDSON3 created

Creating GRANDDAUGHTER1 Looking for parent... Child adopted by SON1
GRANDDAUGHTER1 created

Creating GRANDDAUGHTER2 Looking for parent... Child adopted by DAUGHTER1
GRANDDAUGHTER2 created

Children found, deleting...
Children found, deleting...
No children to delete GRANDSON1 has been deleted

child deleted
No children to delete GRANDDAUGHTER1 has been deleted

child deleted
SON1 Resource deleted
SON1 has been deleted

child deleted
Children found, deleting...
No children to delete GRANDSON2 has been deleted

child deleted
No children to delete GRANDSON3 has been deleted

child deleted
No children to delete GRANDDAUGHTER2 has been deleted

child deleted
DAUGHTER1 Resource deleted
DAUGHTER1 has been deleted

child deleted
No children to delete DAUGHTER2 has been deleted

child deleted
GRANDFATHER Resource deleted
GRANDFATHER has been deleted
\$\endgroup\$
  • \$\begingroup\$ Why a special class for this purpose? What's wrong with using std::unique_ptr (or containers thereof) where needed? \$\endgroup\$ – Pavel Minaev Jan 11 '12 at 19:13
  • 9
    \$\begingroup\$ "Simple, lightweight, deterministic “garbage collection”" - RAII. Read up on it, single most important idiom in C++. \$\endgroup\$ – Xeo Jan 11 '12 at 19:13
  • \$\begingroup\$ @KennyTM: Ah I did have that lingering doubt.Please mark it the appropriate migration. \$\endgroup\$ – Als Jan 11 '12 at 19:15
  • 2
    \$\begingroup\$ Simple, light-weight deterministic memory management is C++. \$\endgroup\$ – Kerrek SB Jan 11 '12 at 19:19
  • 1
    \$\begingroup\$ If you're going to derive from UObject (and I see little point if you aren't) you should make the destructor virtual. \$\endgroup\$ – Anton Golov Jan 12 '12 at 0:58
4
\$\begingroup\$

I would agree with the commenters that smart pointers or other forms of RAII are a more traditional solution and generally to be preferred.

And the virtual destructor is critical, otherwise your derived classes' destructors will never be called -- just the UObject destructor and deallocator.

But let's try to look at this approach "on its merits".

An obvious practical downside of the UObject approach is that it not only allows you to skip explicit deletes, it forces you to do so to prevent double deletion. So, each dependent UObject regardless of its fruitful lifetime has to persist in memory until the root object goes out of scope. That transient memory bloat seems like it would limit the usefulness of this approach. In any case, you'd probably want to have a private operator delete to prevent accidents.

Actually, since only heap-allocated objects need a parent, it would seem to make more sense to have the passed parent pointer and the "adopt" call in a custom operator new rather than in the constructor. That takes it completely out of the root UObject constructor code path and allows the parent pointer to be asserted non-null for dependent (heap allocated) objects.

Since the entire hierarchy for a given root object is reclaimed essentially at once, the only point to having a hierarchy at all seems to be so that new UObjects can be added by attaching themselves to any visible UObject, so they do not need visibility to the root UObject. In situations where the root object remains visible, there is no benefit to using other UObjects as parents, so the root object may as well be a specialized "memory pool" object that implements the adopt and chained deletion. So UObjects would only have to implement a constructor (or operator new).

But even if this "transitivity" effect of being able to lose sight of the root object is desired, it doesn't require any kind of std::list fan-out. It could be implemented more efficiently with a single UObject* member that implemented a singly-linked list. Each child UObject could simply insert itself into the chain after its parent and before its youngest sibling (if any). The root object would just have to iterate over the linked list and extract and clear the link (to avoid recursive stack depth issues) before calling delete.

Another factor that distinguishes this solution from the traditional solutions is that it establishes memory management relationships that are managed completely independently of other "application domain specific" inter-object references. This appears to be the intent of the approach, that memory resource dependencies be tracked separately and generically in a base class cued solely by constructor calls and unaffected by the dynamics of application domain specific object relationships.

One downside is that UObjects would likely need to avoid being referenced by pointer members of long-lived non-UObjects or by UObjects with longer-lived root objects. The other downside is that a hierarchy of UObjects may well have other relationships whose pointer representations would be largely redundant with their UObject relationships. The corresponding upside to "smart pointers" is that the same pointer representation often does double duty as a reference to "related application information" and a reference to "associated memory resources", which is why they translate "application domain relationship" changes into memory management actions. Commensurate memory management is tailored in as a cross-cutting aspect of each dynamic reconfiguration of the objects.

UObjects that share a root can freely reference each other through containers of pointers without lifetime issues, but any pointers to the containers from UObjects would still need to be memory managed using traditional methods or the containers used would need to be grafted into the UObject hierarchy as templated subtypes, which might prove clumsy.

BTW, speaking of containers and traditional memory management methods, the piecemeal pop_front deconstruction of a std::list is a little wasteful considering that the list is at end-of-life. A simple iterator would have served, followed by delete.

\$\endgroup\$
  • \$\begingroup\$ Well, isn't the UObject essentially an implementation of RAII? I have plenty to add to my response so I will post an answer, comment space will not suffice... Please see my answer and feel free to elaborate further. \$\endgroup\$ – dtech Jan 12 '12 at 9:23
2
\$\begingroup\$
    std::list<UObject*> *children;

There's not much point in having a pointer to your list. Just put your list in your object. That'll simplify the code because it will be deleted and created automatically. Also, you should probably use a std::vector<>, They std::vector will be faster for anything except inserting/deleting in the middle of the list which you don't do anyways.

    std::cout << "Children found, deleting... \n";
    while(!children->empty())
    {
        delete children->front();
        children->pop_front();
        std::cout << "child deleted" << std::endl;
    }
    delete children;
    std::cout << name << " Resource deleted" << std::endl;

Since you are destroying the container anyway, emptying it as you go through it not useful. Instead use a for loop with iterators to delete all the entires.

What are you missing is a way to delete part of your tree. You might say close a window. You want that Window and all its children to be deleted. But that window has a parent. How do you delete that window?

\$\endgroup\$
  • \$\begingroup\$ By calling a delete method I guess. The class is still in the making, there will be additional member functions. Emptying the list upon deletion is pointless indeed. I will investigate migrating the UObject to use a std:vector if the container meets my requirements. For UObject I do not need insertion in the end, but there are UParent and UPolygamist which can be arranged, removed and so on, and as children are aware of their parent in contrast to UObject which is fixed and static and blind as a child, only able to see its own children. \$\endgroup\$ – dtech Jan 12 '12 at 15:43
  • \$\begingroup\$ Even without a delete method I can still create either dynamic UObjects and call delete to kill it and its children or simply have the lifetime managed by the local scope, nevertheless there will also be a method to delete an entire branch. \$\endgroup\$ – dtech Jan 12 '12 at 15:55
1
\$\begingroup\$

First let's get to some basics here.

If your plan is to derive anything from UObject it will need a virtual destructor. At present I don't see anything being derived from it, and if needs be you could use a recurring template pattern here so you don't need all your classes to inherit from a single one, which can become a nightmare in multiple-inheritence issues.

In that case you would create a template of some kind of tree that has children:

class MyClassA : public TreeObject<MyClassA>

where TreeObject handles all the parent-child relationships while MyClassA has specifics to how this class is used, and this is called the "recurring template pattern". Note that TreeObject template does not need a virtual destructor but would probably have a protected destructor, and MyClassA will only need a virtual destructor itself if it is going to be a base class, i.e. have virtual functions and other classes deriving from it that will be in this tree.

You also need to enforce the "rule of 3", i.e. you have overloaded the destructor so you need to handle copy-construction and assignment, and my guess is that your objects will be non-copyable.

Another of your noticeable issues is that your child objects must be created "on the heap", i.e. with new. They should therefore also almost certainly have a protected destructor which will automatically enforce this.

My next issue is what happens if you want to destroy a child node because it does not seem possible to mark it deleted in the parent.

My final issue is how you take care of destroying the parent. As we have determined, child nodes can only be destroyed by their parents but this node doesn't have any parents.

Of course, given we have decided that we are going to give the class a protected destructor, we won't be calling "delete" on the child but we can give it a method destroy() which will know what to do, i.e. it will be able to inform its parent that it is being deleted and should be removed from the list (but not deleted). Of course this will be a linear action on the parent to find this child.

Note that this is quite hard to handle properly even with smart-pointers. For example, one thing you certainly cannot do is have the parent have a collection of shared_ptr of the children and the children have shared_ptr to the parent or you have a circular reference. A better way to manage it would be a node class that manages the parent-child relationship, and could even contain the list iterator that hosts the child so that when if a child is deleted on its own, it would "know" the list node that its parent must remove. The good thing about list nodes is that (1) their iterators remain valid even when you modify the list and (2) they are constant time to remove even when they are in the middle of the list.

Therefore if you are going to delete child nodes, std::list is actually quite a decent collection to use on the parent this time.

In essence, what you are creating is a tree. C++ doesn't have a standard tree collection. (It has std::set and std::map that are implemented with trees but does not expose its tree structure).

A generic tree was proposed on boost back in 2005:

http://lists.boost.org/Archives/boost/2005/02/80775.php

and it might be worthwhile looking at the discussion that took place in there.

\$\endgroup\$

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.