patterncppMinor
Simple, lightweight deterministic "garbage collection"
Viewed 0 times
simplelightweightcollectiondeterministicgarbage
Problem
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
The idea of
The
Header:
Implementation:
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");
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 *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 adopt(this);
}
else
{
std::cout empty())
{
delete children->front();
children->pop_front();
std::cout push_back(child);
std::cout ;
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");
Solution
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
But let's try to look at this approach "on its merits".
An obvious practical downside of the
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
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
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
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
BTW, speaking of containers and traditional memory management methods, the piecemeal
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.Context
StackExchange Code Review Q#7688, answer score: 4
Revisions (0)
No revisions yet.