Click here to Skip to main content
15,860,861 members
Articles / Programming Languages / C

new(local) for safer and simpler code with no need for smart-pointer template wrappers

Rate me:
Please Sign up or sign in to vote.
4.65/5 (20 votes)
25 Oct 2012CPOL8 min read 91.5K   387   26   65
reduce bugs and memory leaks by using new(local)

Introduction

Pointers always envied automatic cleanup that is in C++ provided only for big fat slow statically allocated (automatic) 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 (I.E. associated objects/memory will get destroyed with the scope). Plus your code will now be smaller (and generated binary) and simpler. I got this idea about pointers deserving automatic cleanup love too in my other kinda lame article about new operator

C++
struct Object : Scope::Obj {
};

Scope global;
// entering scope
void proc() { 
    Scope local;
    char*  text = strdup(local,"text"): // malloc based memory have automatic cleanup too
    
    Object* obj = new(global) Object(); // obj will get destructor called on program exit 

    Object* array[1000]={0};            //sorting pointers is fast compared to array of objects
    for(int i=0;i<1000;i++) {
        array[i] = new(local) Object(); // creates object associated with "local" scope 
    }
}
// we left scope so "Scope local" and all associated object /memory 
// gets destructors called/freed in proper order

Why Manual Cleanup is Source of Bugs

Automatic cleanup provided for automatic 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 conditional statements or bad multithreaded code.

Performance Point of View

So we definitely want automatic cleanup that is provided for automatic 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 automatic objects by automatic cleanup when leaving scope? Yes we can if we implement concept of scopes also for dynamic objects.

Simpler and faster alternative to smart pointers

Not that I think that STL/Boost are not important and useful. They are in zillion cases since it doesn't make sense to reinvent things again and again.

Little advantage of scope.h is that you don't need kinda heavy dependency on stl/boost just for simple thing like pointer. Code simplicity and clarity keeps code fast and bugs at bay. Why? In the sample code below it is clear what is going on with basic pointer types not limiting you to any storage imaginable.

C++
               // by keeping simple pointers it's always obvious what is going on
               // and how efficiently.
 ptr1 =  ptr2; // just pointers (4-8 bytes) are copied ie MOVE semantics
*ptr1 = *ptr2; // whole objects (??? bytes) are copied ie COPY semantics
               // ie you decide  when you copy or move by clear non macro-encrypted syntax.

With smart pointers on the other-hand the fact when they actually copy or move is one big jungle they result to zillion unnecessary function calls (just single-step in debugger) where there was single instruction per cpu clock with simple pointers before and most of them even don't support storage to arrays

Most of them invoke multiple function calls in place of simple instructions and simply don't work in containers. some require newest c++11 compiler or big template libs resulting to big executables. All make code unusable by others since libs/dlls with stl/boost dependency are incompatible on binary level not having any concept of stable interface.

auto_ptr has long and rocky stl history and is nowadays very bad choice for pretty much anything. It's pointer with copy and with move semantics and ownership (=auto-delete) can't be stored in containers scoped_ptr is a auto_ptr without copy and without move semantics can be stored in containers and requires boost. It adds a level of safety. Memory created will exist only for that scope and no more, thus you get compile-time protection against attempting to move it or transfer it.

unique_ptr is a auto_ptr without copy but with move semantics can be stored in containers requires C++ 11 compiler. Ownership is movable, shared_ptr. Require c++ 11 compiler and are reference counted ie. cleanup when all references are gone.

weak_ptr, intrusive_ptr ... whatever new ones will come.

With smart pointers you need to wrap every pointer reference ie change all the code to alien lenghty syntax always worrying about state machine you just created. Take for example this guy having trouble sorting simple array of pointers which he normally wouldn't have problem to create but now obviously having no-idea what state machine he actually created he is sitting in forums getting 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 ..."

It is unbelivable what complexity we vere able introduce to ehm... "simplify our life"

The real solution is to have smart pointers build in C++ language and not create zillion of weird templates supporting this and not that. For example let Object *~ be declaration of smart pointer. Will have destructor called going out of scope and all of the copy move is already part of low level pointer arithmetics. No zillion of template wrappers/libs/operators period. I am right now attempting to implement it in fantastic CLang /gcc compilers as an interesting experiment. Visual c++ is closed source so Thank's god for whole open source movement.

But until then with scopes you just derive your objects from Scope::Obj in one line and that's all. 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();// will not allocate and will leave prematurely due to memory 
                            // exhaustion exception. But no memory leak will happen since scope
                            // deallocates ptr1 on scope destruction as part of object
                            // destruction that follows this constructor exception
    }
}

So if you are like me preferring things small clear simple and fast. If you don't like obfuscation for simple things and like to know by fast 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 automatic or global objects actually work? Well. It works by simply adding pointer of every automatic 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 ( dynamically allocated via new/malloc ) 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++ doesn't allow to get destructor addresses. So the only solution I come up so far is to use virtual destructor in all stored objects so polymorphism 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 thread-safe version of scope called TScope (useful 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.

C++
struct Scope { // This is Just simple linked list
       struct Obj { virtual ~Obj() {} };
       Scope*    prev;  Obj* ptr;
       Scope() : prev(0) ,   ptr(0) {}	
      ~Scope() { ptr->~Obj(); free(ptr); delete prev; } // deleting all childs in reverse order on exit
};

inline void * __cdecl operator new(unsigned int size,Scope& scope)
{
    void *ptr = (void *)malloc(size); // we add all new objects to this linked list
    if(scope.ptr) { Scope* prev=0; prev=(Scope*)calloc(sizeof(Scope),1); *prev=scope;
    scope.prev=prev; }
    scope.ptr=(Scope::Obj*)ptr; 
    return(ptr);
};

Limitations of this solution

In C language Scope.h usage is very simple and straightforward. You associate pointers with scope and they are freed automatically. In C++ thou you need to call destructors first. Unfortunately C++ doesn't support getting address of destructor. So as slight inconvenience. To support automatic cleanup your objects need to be derived from Scope::Obj. That being said adding one derive statement in class declaration is one changed line of code ie less work and changes than going about replacing every occurrence of pointer of that class with long alien syntax as you need with smart-pointers.

Exception Handling

To let Scope cleanup everything for us no matter what exception happens we use standard exception handling statements. Usually you wanna cleanup happen before exception handler invocation ( smart_ptr behavior ) keep Scope object declared within try {} statement otherwise keep it outside and cleanup will be postponed after exception handler execution. The later case can be useful too.

C++
proc() {    
    try {
        Scope local;
        Object* array[1000]={0};            //sorting pointers is fast compared to array of objects
        for(int i=0;i<1000;i++) {
            array[i] = new(local) Object(); // creates object associated with "local" scope 
        } 
    } catch(...) { 
        printf("no manual cleanup needed anymore");
    } 
}

In case of exception as part of exception unwinding C++ calls our scoped version of delete which deregisters failed object from scope so when scope is destroyed this object is gone (no free or destructor called)

Example code

C++
#include <stdio.h>
#include <stdlib.h>
#include <windows.h>
#include "Scope.h"   

Scope global; // Deallocates/Destroys all associated objects on program exit no matter 
              // what exception or error in program happens

struct A : Scope::Obj {// As for supported objects. They must be derived
              // from Scope::Obj to have virtual destructor as the first virtual method 
              // so it is first in vtable and correct call is constructed on cleanup
              // In a sense we are keeping pointers to destructors as known this way
       A(){ printf("\n  A %x",this); }
      ~A(){ printf("\n ~A %x",this); } 
};

void Test() {
    Scope local; // all objects associated with scope will be deallocated by leaving procedure
    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();  // this will get destructor called on progam exit
}

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 derive from Scope::Obj is slight inconvenience considering what you will gain. Also if you want to auto-deallocate memory from malloc/calloc/strdup just use scoped versions from scope.h in zip file. Off coarse for explicit deallocation 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.

Possible Performance Improvements

Usual scope contains around 10 objects so there is no performance difference between current linked list or array. But for large arrays 100000 etc. using preallocated array is radically faster and more space efficient alternative. I will rewrite it to array and add optional preallocation size parameter as Scope scope(items) in next iteration.

Building automatic cleanup to c/c++ language via experimental switch

I am trying to implement smart pointers (special pointer that has destructor/free called when leaving scope or containing object is destroyed) as build in to c and c++. so low level os c/c++ developers can produce smaller and safer code too without big template libs.


GCC: -fsmart-pointers switch implementation status. So far I am just studying how is automatic or global object cleanup actually implemented. In first phase gcc transforms source to so called gimple form
struct A{ int a;
   ~A(){}
};
int main(int argc,char**argv) {
    A a[12];
    return 0;
}

gcc -O0 --dump-tree-gimple test.cpp produces form in which generated code for automatic cleanup of automatic objects (Loop generating this pointers and calling destructors) is clearly visible

try
  {
    D.1868 = 0;
    return D.1868;
  }
finally
  {
    {
      struct A * D.1866;
      D.1866 = &MEM[(void *)&a + 48B];
      <D.1869>:
      if (D.1866 == &a) goto <D.1870>; else goto <D.1871>;
      <D.1871>:
      D.1866 = D.1866 + -4;
      A::~A (D.1866);
      goto <D.1869>;
      <D.1870>:
    }
  }

So far it seems that this code is generated gcc\cp\init.c in proc build_vec_init

I am open to interesting new ideas or suggestions. But help with gcc switch implementation is welcomed most.

Latest Changes

  • 20.Oct.2012 Added pure C language version
  • 25.Oct.2012 Changed from keeping pointers in linked list to pre-allocated arrays for speed.

License

This article, along with any associated source code and files, is licensed under The Code Project Open License (CPOL)


Written By
Software Developer (Senior)
Slovakia Slovakia
Past Projects:
[Siemens.sk]Mobile network software: HLR-Inovation for telering.at (Corba)
Medical software: CorRea module for CT scanner
[cauldron.sk]Computer Games:XboxLive/net code for Conan, Knights of the temple II, GeneTroopers, CivilWar, Soldier of fortune II
[www.elveon.com]Computer Games:XboxLive/net code for Elveon game based on Unreal Engine 3
ESET Reasearch.
Looking for job

Comments and Discussions

 
GeneralRe: My vote of 2 Pin
Ladislav Nevery2-Oct-12 14:26
Ladislav Nevery2-Oct-12 14:26 
GeneralRe: My vote of 2 Pin
Philippe Mori3-Oct-12 2:09
Philippe Mori3-Oct-12 2:09 
GeneralRe: My vote of 2 Pin
Espen Harlinn3-Oct-12 2:10
professionalEspen Harlinn3-Oct-12 2:10 
GeneralRe: My vote of 2 Pin
Ladislav Nevery3-Oct-12 4:00
Ladislav Nevery3-Oct-12 4:00 
QuestionUndefined behavior Pin
John Bandela2-Oct-12 5:22
John Bandela2-Oct-12 5:22 
AnswerRe: Undefined behavior Pin
Ladislav Nevery2-Oct-12 6:53
Ladislav Nevery2-Oct-12 6:53 
GeneralRe: Undefined behavior Pin
John Bandela2-Oct-12 7:59
John Bandela2-Oct-12 7:59 
GeneralRe: Undefined behavior Pin
Ladislav Nevery2-Oct-12 13:05
Ladislav Nevery2-Oct-12 13:05 
John Bandela wrote:
To do what you are doing, you have to require that all classes used with your scope derive from Scope::Obj


Either derive or keep virtual destructor as first virtual method. Sorry I forgot to mention it. I updated article with this info.

Ie it's not relaying on undefined behavior but yes it's kinda weird requirement;)


John Bandela wrote:
Thus you are using delete on malloc memory.


Althou all compillers internally use malloc in operator new.
This is not nor will be garanteed.
You are right I updated article and zip with explicit call to destructor + free

Thx for spotting this
"There is always a better way"

GeneralRe: Undefined behavior Pin
Philippe Mori2-Oct-12 12:20
Philippe Mori2-Oct-12 12:20 
GeneralRe: Undefined behavior Pin
Ladislav Nevery2-Oct-12 22:03
Ladislav Nevery2-Oct-12 22:03 
QuestionThis is a reasonable approach for many problems. Pin
JackDingler2-Oct-12 4:51
JackDingler2-Oct-12 4:51 
AnswerRe: This is a reasonable approach for many problems. Pin
Ladislav Nevery2-Oct-12 6:56
Ladislav Nevery2-Oct-12 6:56 
GeneralRe: This is a reasonable approach for many problems. Pin
JackDingler2-Oct-12 8:02
JackDingler2-Oct-12 8:02 
GeneralRe: This is a reasonable approach for many problems. Pin
Ladislav Nevery3-Oct-12 4:41
Ladislav Nevery3-Oct-12 4:41 
GeneralRe: This is a reasonable approach for many problems. Pin
JackDingler3-Oct-12 4:55
JackDingler3-Oct-12 4:55 
GeneralRe: This is a reasonable approach for many problems. Pin
Ladislav Nevery3-Oct-12 5:31
Ladislav Nevery3-Oct-12 5:31 
GeneralRe: This is a reasonable approach for many problems. Pin
JackDingler3-Oct-12 6:15
JackDingler3-Oct-12 6:15 
GeneralRe: This is a reasonable approach for many problems. Pin
Ladislav Nevery3-Oct-12 9:01
Ladislav Nevery3-Oct-12 9:01 
Questionnice ! Pin
Sergey Vystoropskiy1-Oct-12 3:28
Sergey Vystoropskiy1-Oct-12 3:28 

General General    News News    Suggestion Suggestion    Question Question    Bug Bug    Answer Answer    Joke Joke    Praise Praise    Rant Rant    Admin Admin   

Use Ctrl+Left/Right to switch messages, Ctrl+Up/Down to switch threads, Ctrl+Shift+Left/Right to switch pages.