Propagate deletion of C++ Objects

Last year, I spent some time rewriting internals of how groups are handled in our CAD software. Before that, groups were just to help with the selection of elements that belong together. The grouping was only one level deep, and the elements were organized flat on the document. All the calling functions had to uptate the relationships in the pointers and containers which were publicly available. This was a constant source of bugs and completely unsuitable for nested symbols that we wanted to import.

After this initial rewrite, groups can be nested to whatever depth that makes sense. They are organized in a tree structure and additionally, on the drawing level there is a flat multi-index container for fast lookup by id and for retaining the drawing order. From the c-style design, I switched to object oriented, encapsulating the containers and providing add-/remove functions and a memberspace to access the elements. With this rewrite I started to introduce some events. To begin with, only OnChildAdded() and OnChildRemove(). One of the design goals was that one could delete an Entity somewhere, and all the structures would update automatically. That required some tweaking with the destructors.

Let’s say we have two main base classes Entity and EntityOwner. Line would descend from Entity, Drawing from EntityOwner and Group from both, enableing the nesting tree structure. The destructor of Entity calls RemoveChild on it’s parent. As the parent stores Entity pointers in it’s child collection, that’s totally ok. Group as well as all other descendants of EntityOwner are required to call DeleteChildren from their destructors. That was a little caveat to make sure the proper OnChildRemove event was called. If that happened only in the destructor of EntityOwner, the EntityOwner to which the Entities report would no longer be a Group, and thus the Group’s OnChildRemove would not be called. That wouldn’t be such a problem with the group, but a Drawing which maintains the flat multi-index needs to get these events. That’s no big deal, as there aren’t that many descendants of EntityOwner. But the downside is, that it can’t be automatically enforced by the type system, so I documented it with some comments.

The mechanisms were tested extensively, and worked reliably. Later I started to extend the events. And propagated them further up to the Document. The OnChildRemove event was used to clear associations between hatches, the key (handle) map to the grapics system and in some cases also Undo/Redo. This started to simplify things, but it’s not finished throughly yet. In the meantime, a co-worker discovered problems when deleting Entities. The cleaning up on the Document was sometines not done properly. The problem was that there was type dependant code, while the event was called out of the Entity destructor, at which time a Line is no longer a Line, but only an Entity. That’s essentially a similar problem as with the EntityOwner above.

The first approach was to make all destructors of Entity descendants private, and add a non-virtual Destroy method. This method would call another non-virtual OnDestroy method of Entity which does the de-registration, and then the Entity would be deleted. This side of the fence worked and we didn’t care that it was no longer possible to allocate Entities on the stack. But there was another problem. This didn’t play nice with smart-pointers. std::auto_ptr doesn’t have custom deleters. boost::shared_ptr does, but can’t release raw pointers. Loki could probably do both but gets too complicated. The std::unique_ptr of the upcoming new standard proved a solution here. I found an implementation for older compilers that mimicks the intended behavior as good as it can. But then I didn’t find a good solution for boost::ptr_vector of Entities.

The second approach was to use something similar to what I did with EntityOwner above. But as we have more than 50 classes descending from Entity, enforcing such a policy by hand was not feasible. So I created some “amok code”. Just joking, but I use macros really only if I can’t find a better solution. Now every class descending from Entity is required to include the following macro, and put the implementation that used to be in the destructor into OnDestroy();

#define ENTITY_TEARDOWN(classname)     
    public: 
        virtual ~classname() { Unregister(); OnDestroy(); }
    private:
        void OnDestroy();

For Line, that would look something like this:

class Line : public Entity
{
    SERIAZIBLE(Line);
    VISITABLE(Line);
    ENTITY_TEARDOWN(Line);
public:
    Line();
    Line(const Line& lin);
    Line(const Point& p1, const Point& p2);

...
};

Posted

in

,

by

Tags: