Introduction
Pointers always envied automatic cleanup that is in c++ provided only for big fat slow Static Object or their arrays stored by value. In this article we will introduce concept of scopes also for dynamic objects and scoped version of new/malloc to take advantage of those scopes. Ie associated objects/memory will get destroyed with the scope. Plus your code will now be smaller and simpler. I got this idea about pointers deserving automatic cleanup love too in my other article about
new && operator
Scope global;
{
Scope local;
char* text = strdup(local,"text"):
Object* obj = new(global) Object();
Object* array[1000]={0}; for(int i=0;i<1000;i++) {
array[i] = new(local) Object(); }
}
Why manual cleanup is source of bugs.
| Automatic cleanup provided for static objects saves time lines of code and a lot and I mean really lot of bugs. Bugs by forgetting to match all allocations with deallocations.
Bugs by memory being leaked when exception or error handler exits something somewhere prematurely and not all control paths contain correct number of release statements. |
Bugs by releasing objects twice or more and corrupting memory by confused condition statements.
Performance point of view
So we definitely want automatic cleanup that is provided for static objects. But pretty much any program serious about performance will store objects by pointers and not by value because price for reallocation insert or sort is horrendous as you can see in benchmark
here.
So can't we have both? Performance and efficiency of pointers and safety and simplicity of static objects by automatic cleanup when leaving scope? Yes we can if we implement concept of scopes also for dynamic objects.
Simpler alternative to smart pointers
Another nice advantage of scopes is that you don't need kinda heavy dependency on stl/boost just for simple thing like pointer. Keeping simple easy to debug code and libs/dlls usable by others. Just insert scope to your class and use scoped de/allocators. All your data will be properly deallocated exactly like with smart pointers even when constructor fails in the middle during exception etc.
struct A {
Scope local;
B* ptr1;
B* ptr2;
A() {
ptr1=new(local) B();
ptr2=new(local) B();
}
}
Reinventing Wheel
Turns out that I am not the first one thinking along those lines and Boost template library already provides us ptr_vector. vector of shared_ptr or shared_array.
Templates are great when you are not creating library (stl/boost are binary incopatible between versions/compillers) and when you don't mind wrapping every variable to template pretty much rewriting all your code and when everything is working. Take for example this guy having trouble sorting simple array of pointers and geting responses like
"...The IS specifies that a predicate should not assume that the dereferenced iterator yields a non-const object. Arguments should therefore be taken by reference-to-const or by value. The IS also specifies that
algorithms that take function objects as arguments are permitted to copy those function objects. So in most cases, the overloaded function call operator would be a const member function ..." | |
Hmm ... It is unbelivable what complexity we vere able introduce to ehm... "simplify our life"
So if you are like me preferring things small and simple so you know by simple look at source what is going on.
Let's try simpler but I hope equally functional alternative.
Let's Scopes for dynamic heap objects be born
| So how does the automatic cleanup of static objects actually work?
Well. It works by simply adding pointer of every static object within scope to internal invisible linked list and when leaving calling destructors for each of them in reverse order. |
Can we make something similar for dynamic objects?
Why not. We can have array of void pointers and store pointers to various object types in one single array.
The only issue are calling proper object specific destructors. Unfortunately as far as I know c++ doesnt allow to get destructor adresses. So the only sollution I come up so far is to use virtual destructor in all stored objects so polymorphysm selects proper one for us.
Implementation of Scope.h
This code snippet bellow is kept simple with compressed formating just for purpose of article so it is clear what is going on. Complete implementation along with scoped versions of new delete strdup malloc calloc free and threadsafe version of scope called TScope (usefull for using for example global scope from multiple threads etc) is in Scope.h that is attached in zip file with Example.cpp on top of the article. I did not attached project files since most of you will not be able to open vs2012 anyway.
struct Scope { struct Obj { virtual ~Obj() {} };
Scope* prev; Obj* ptr;
Scope() : prev(0) , ptr(0) {}
~Scope() { delete ptr; delete prev; } };
inline void * __cdecl operator new(unsigned int size,Scope& scope)
{
void *ptr = (void *)malloc(size); if(scope.ptr) { Scope* prev=0; prev=(Scope*)calloc(sizeof(Scope),1); *prev=scope;
scope.prev=prev; }
scope.ptr=(Scope::Obj*)ptr;
return(ptr);
};
|
Beware: only objects having virtual destructor shall be associated with scope or gates of hell will open. |
Example code
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "Scope.h"
Scope global;
struct A {
A(){ printf("\n A %x",this); }
virtual ~A(){ printf("\n ~A %x",this); }
};
void Test() {
Scope local;
A* array[3]={0};
for(int i=0;i<3;i++) {
array[i]=new(local) A();
}
char* text=strdup(global,"this will get deallocated on program exit");
A* a=new(global) A();
}
void main() {
Test();
}
Output:
A 689718 A 68b110 A 68b198 A 68b220 ~A 68b198 ~A 68b110 ~A 689718 ~A 68b220
Points of Interest
The fact that so far I managed to autodeallocate only objects with virtual destructor kinda sadens me. But I hope that some way to get around this limit is found since deriving from std::string etc just to make it' destructor virtual to allow it's autorelease is kinda unelegant.
Also if you want to autodeallocate memory from malloc/calloc/strdup just use scoped versions from scope.h in zip file. Off coarse for explicit dealocation you must use scoped versions of free/delete too so they are removed from scope and no another attempt is made to free them when leaving scope