You argue that C doesn't have templates. To my knowledge C doesn't have the new operator and destructors. So in any case you still need a C++ compiler.



It isn't clear to me why this is better than using std::unique_ptr<T> . It seems worse. Your solution requires a second dynamic memory allocation just to manage the object lifetime, while std::unique_ptr<T> does not. Your solution requires virtual destructors, while std::unique_ptr<T> does not. Your solution does not properly handle a destructor that throws, std::unique_ptr<T> does. Your solution does not allow me to move responsibility for pointer destruction to someone else, std::unique_ptr<T> does. Your solution does not allow for different scoping levels within a single function, std::unique_ptr<T> does. Your solution does not allow for destructing a pointer explicitly, std::unique_ptr<T> does. I could probably go on, but I think I've made my point.



The only disadvantage to std::unique_ptr<T> that you claim is that it is complicated, but I disagree. std::unique_ptr<T> 's implementation for g++ 4.7.0 is 560 lines, but that includes all the custom deleter junk. If you want a simple implementation, there is boost::scoped_ptr<T> , which is 130 lines, is ridiculously straightforward, and has none of the shortcomings of your solution. Or you can make your own to do whatever you want.



Your example of "this guy having trouble sorting simple array of pointers which he normally wouldnt have problem" is disingenuous. His problem was const -correctness. Had SortFunctor::operator() been written as operator()(Test*& object1, Test*& object2) (the moral equivalent to operator()(std::shared_ptr<Test>& object1, std::shared_ptr<Test>& object2) with naked pointers), he would have had the exact same issue. shared_ptr made the compile error worse, but was not the source of the problem.



As far as doing this in a language switch goes: You really need to consider the prospective audience. If somebody cares enough about memory usage to be concerned with the possible code bloat that comes from a templated smart pointer, they're going to be more concerned with the memory bloat coming from the shortcomings of your solution: double-allocation, requiring a virtual destructor, etc. In embedded systems, code bloat is far less significant than per-object or per-allocation bloat, since code bloat is at least fixed.



modified 18-Oct-12 11:57am.









C++ Pirate Programmer wrote: It isn't clear to me why this is better than using std::unique_ptr<T> . It seems worse.



This article is an attempt to offer an alternative to c (no templates there) and c++ programmers that can't or don't want use slow and wastefull template wrappers.



I bet you know about price for copy semantics in template based containers.

Yes one day when everybody will have c++ 11 compiller at least heap part of this waste could be reduced (not that it all will and only if objects have move versions of every operator constructor etc.).Since most of the objects are small that means most of the waste is static object mem that even && rvalue operator can't solve.



I also bet you know the price for using wrappers is zillion methods invoked unnecessary and methods invoked in places where there was simple assembly instruction.



Take for example what is going on when you have common compiller and store 1000 shard_ptr's

(very common case since let's say sorting or reallocating 1000 objects stored by value is bad idea)

that is default constructor/constructor/operator=/destructor + 1 wasted copy = around 4000 noninlinable methods invoked. all doing stack push/pops local var reserve/cleanups exception handler de/registrations.



now compare that to storing 2000 numbers (if we use preallocated arrays instead of linked list in scope(which I plan))



C++ Pirate Programmer wrote: Your solution requires virtual destructors,



No problem in C and in most code I can think of in c++ either.

But it's on the readers of article to weight pros and cons and maybe gain another alternative. Which is not entirelly a bad thing I guess.





C++ Pirate Programmer wrote: Your solution does not properly handle a destructor that throws



Yes it does.



proc() { Scope local; try { Object * array [ 1000 ]={ 0 }; for ( int i= 0 ;i<1000;i++) { array [i] = new (local) Object (); } } catch (...) { printf( " no manual cleanup needed anymore" ); } }

C++ Pirate Programmer wrote: Your solution does not allow me to move responsibility for pointer destruction to someone else



the need for shared ptr is rare thing and reference counting is 2 lines of code to object where it is relevant. I actually wana see what is going on.



Side effect of copy/paste programming is that many people use shared_ptr instead of scoped_ptr justbecause everybody proposes them not having idea about unnecessary reference counting going on.



Are you actually happy with zillion of design patterns mixed with automatic cleanup ?

I mean the whole mess with this smart_ptr does this but doesn't do that being inefficient with this but not that. This uses move semantics for this but not that. This uses just copy semantics etc.





C++ Pirate Programmer wrote: Your solution does not allow for different scoping levels within a single function



Yes it does.



Scope local; Object * object = new (local) Object (); { Scope local; Object * object = new (local) Object (); }





C++ Pirate Programmer wrote: Your solution does not allow for destructing a pointer explicitly



Yes it does.



Scope local; Object * object = new (local) Object (); delete (local) object;



C++ Pirate Programmer wrote: As far as doing this in a language switch goes: You really need to consider the prospective audience. If somebody cares enough about memory usage to be concerned with the possible code bloat that comes from a templated smart pointer, they're going to be more concerned with the memory bloat coming from the shortcomings of your solution: double-allocation,



Switch sollution doesn't involve any more overhead that we gladly acceptin c++ for automatic objects. And that is perfectly fine with me.



The switch is first and foremost about removing manual cleanup thus reducing memory leaks double cleanups or memory overwrites(stability) by deallocating wrong object(security) etc and generating smaller code.

No in compiller you have access to all internal info so you don't need any virtual destructors just as you don't need it with automatic objects

"There is always a better way"



modified 18-Oct-12 22:36pm.



I find it nice to see new approaches that are simple (well simpler than heavy duty templated code anyway) and that don't come at a big performance cost or require a lot of code re-write to adopt.



Sure, it may still be preferable to use other constructs in many cases, (frankly I can't follow half of the debate) but it doesn't change the fact that you've just expanded our tool-set, or at least out idea-set.



Thank you for that

Keep trying new stuff





"There is always a better way"



Far too limited to be of use. Does not allow to put pointed-to objects in containers, doesn't solve problem of passing a pointer out of scope (even if you're careful) which shared_ptr, for instance, solves.



SeattleC++ wrote: Far too limited to be of use.



In what way you feel limited?

Please elaborate in concrete example.



SeattleC++ wrote: Does not allow to put pointed-to objects in containers



Why do you think objects used in example can't be stored to containers ?

Please elaborate in concrete example.



SeattleC++ wrote: doesn't solve problem of passing a pointer out of scope



That's because this article is about automatic cleanup when going out of scope.



You are confusing two independent design patterns.

One is concept of temporary lifetime = Scope.

And second is concept of shared resource and Reference counting.



While all of us needs and benefits from first the same can't be said about second.

In other words not every thime you need automatic cleanup you need also overhead of reference counting.



But lot of people actually use shared_ptr instead of scoped_ptr just because it's more agressively pushed in forums in sentences "hey... use shared_ptr it can finally be stored to an array".



This is why I prefer Scope functionality to do Scope functionality.

And when I would need reference counting I would simply write 2 lines of code?

"There is always a better way"







It might be tempting to use this mechanism in a project where you were sure you wouldn't leak pointers out of the managed lexical scope. If the code was sufficiently well documented, it might even make it past a code review. But it would have to be clear to everyone on the project that you were doing something unsafe. If an inexperienced coder tried to use this mechanism, the chance of disaster would be great. That is why I would not recommend this mechanism.



Your basic smart pointer template is only 30-50 lines of C++ You don't have to load all of boost to use smart pointers. If you don't understand the smart pointer idiom well enough to read an existing implementation or write one with semantics you like, you are probably not experienced enough to use your proposed mechanism safely. In this case you should stick with reference-counted smart pointers, or switch to a language like Java where you don't have to think about storage management.



SeattleC++ wrote: <layer>Let me be clear. Your method is far too limited to be of general interest because it is unsafe. The storage is reclaimed at a scope boundary, but plain-old-pointers still point to it. You could too-easily pass your plain-old-pointer to a variable or data structure with longer lifetime, resulting in dangling references to reclaimed storage.





Unsafe? Concept of scopes for dynamic objects is no more safe or less safe then let's say

concept of scopes for objects allocated using static(fixed and known at compiletime) stack memory. How come ?



void * proc() { Object object; return &object; }

You see ? you are in exactly the same risk of dangling pointer with static data as with dynamic data.



Does the fact that we are allowed to pass around pointer to static object mean that static objects are equally unsafe and shall not be used because they allow dangling pointers ?



Definitely not. Exactly the concept of scope and automatic cleanup was big improvement in c++ and stopping their usage would be big step back.



You will still need to use your brain and be aware what you are doing.





SeattleC++ wrote: By contrast, reference counted smart pointers have dynamic lifetime. Other kinds of smart pointers control the ownership and lifetime of the pointed-to storage as well and better limit leaking plain-old-pointers.



Reference counting is pretty much 2 lines of code.

I have no problem adding them anytime just for rare cases that need them.



So what exactly makes them better? Other than the fact that you make code less readable more complex( templates are miniature statemachines added to each variable they wrap around ) with hard to read simple codelogic (did you read some template sources using template deductions?) spread over zillion files.

Then there is fact that for pretty much any operation +=() you name it tons of calls pushes on stack are made instead of using the tool that does it most efficiently. the cpu instructions and registers.



Go ahead and ask any programmer using so called smart pointers whether his data are being uselessly copied over and over or moved? I bet most of them will have no idea due to way templates are written.



Ask him how much effort it will take him to make it to use the new move semantics in c++11 instead of old and wastefull copy.

Yes sir it means pretty much every copy constructor and assign(along with others) operator need to have duplicite move rvalue reference && variant that still allows only moving dynamic not static part of objects. That is the performance price for wrappers. All that effort and code just attempting to simulate data transfer efficiency you already have with low level pointer.





SeattleC++ wrote: If you don't understand the smart pointer idiom well enough to read an existing implementation or write one with semantics you like, you are probably not experienced enough to use your proposed mechanism safely.





"There is always a better way"



modified 18-Oct-12 3:36am.



Ladislav Nevery wrote: Unsafe? Concept of scopes for dynamic objects is no more safe or less safe then let's say

concept of scopes for static objects. How come ?

void * proc() { Object object; return &object; }

You see ? you are in exactly the same risk of dangling pointer with static data as with dynamic data.



Well, you would be, except this is not static data, it's automatic data. Static data is directly visible within the lexical scope where it's defined, but its lifetime persists until main() returns or exit() is called. If you add the "static" storage class specifier to your declaration of object, you can pass a pointer out of the lexical scope, safe from fear of a dangling reference.

Ladislav Nevery wrote: So what exactly makes [reference counted smart pointers] better?

What makes them better is that the storage is managed correctly no matter the lifetimes of the pointers *and* dangling smart pointer references cannot happen. This is my definition of "safe".

Ladislav Nevery wrote: did you read some template sources using template deductions?) spread over zillion files.

Yeah, template code can be pretty hairy. Its definitely not for beginners, just like inventing your own storage management mechanism is not for beginners. The longer this conversation goes on, the more it proves this point.



Thanks for an interesting discussion.



SeattleC++ wrote: Ladislav Nevery wrote: Unsafe? Concept of scopes for dynamic objects is no more safe or less safe then let's say



concept of scopes for static objects. How come ?



void * proc() { Object object; return &object; }

You see ? you are in exactly the same risk of dangling pointer with static data as with dynamic data.







Well, you would be, except this is not static data, it's automatic data.



(I guess word automatic alloc is better wording choice then static allocated memory. ) But. Yes we can shift to wording instead of topic



I gave you concrete performance and code clarity explanations why exactly smart pointers are not better not just generic statements like "better"



SeattleC++ wrote: *and* dangling smart pointer references cannot happen



Really?

std::unique_ptr<Object> ptr(object); delete object;

or

std::unique_ptr<Object> proc() { Object object; std::unique_ptr<Object> ptr(&object); return ptr; }



What do I see ? dangling smart pointer? You see it doesn't matter wether object was dynamic automatic or smart.



Because programmers doing Manual Cleanup repeat their own bad habits misteakes and bad design practices. And the ones doing dangling pointers before are just as likely to make them again.



It's automatic cleanup idiom that reduces the error landscape by reducing the manual cleanup. And it doesn't matter wether you use templates or other means result will be significantly more robust code.





SeattleC++ wrote: Yeah, template code can be pretty hairy. Its definitely not for beginners



You think people not using templates like operating system /kernel/driver hackers or doing any c/c++ static library useable by others (forget stl/boost) are begginers?



Keeping code state machine simple is important design philosophy tested by time.



When things get complex the last thing you wana have is simple code logic spread over many sources/headers.



You wana have as much relevent codelogic in one place as possible and have as compact and robust error handling as possible.



I use "Occam's razor" whenever I can.



Because projects grow like trees. People with varying degree of experience start working in paralell on the same code. Belive me. You will pay for complex code dearly in maintenance and bugfixing time since you are never alone on the project.

"There is always a better way"



modified 18-Oct-12 8:02am.



Hi Ladislav,



At first, nice article to show power of C/C++ and how can these two languages can be either used or misused.



I have only one question: Did you consider in your design that the constructor of any class (derived from your Scope::Obj; or any class which has a virtual destructor and is instantiated by calling your scope functionality) can throw exception?



struct A { A() { throw std::bad_alloc(); printf( "

A %x" , this ); } virtual ~A() { printf( "

~A %x" , this ); } }; void Test() { try { 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(); } catch (...) { } }







Because C++ standard says that any class constructor or assignment operator can throw exception. So, when the class A constructor (from your example code) throws any exception (that means, the instance of A is not instantiated and the allocated memory should be just simply deallocated without calling of class A destructor) you are going to call the virtual destructor of class A; the is_obj is unfortunately 1 and the line if(is_obj) ptr->~Obj() from your ~Scope() is executed . This behaviour leads to another exception!!!



Please, try to solve this problem or just write a comment to your article: "The proposed solution is not exception safe!"





You should keep scope declaration above (outside) of try catch otherwise it can't serve it's role. Ie do cleanup whatever happens.

I added this important info to article.



Objects are automatically deregistered from scope thank's to automatic call of placement delete(defined in scope.h) called from within _unwind. And for deregistered objects obviously no destructor is called when leaving scope. I uploaded updated code and article



I like constructive criticism that makes article better. Thx again.

"There is always a better way"



modified 16-Oct-12 15:06pm.





I hope that you agree that the behaviour of your allocation handler and generic template "Smart pointers" shall be the same in the case of exception handling (when the exception is raised in the constructor).



~Scope() { if (ptr) { if (is_obj) ptr->~Obj(); free(ptr); } delete prev; if ( lock ) delete lock ; }





I will try to explain that in detail and it's up to you to consider my comment or not. So, I will show you the problem with a little bit modified code of your example.



#include <stdio.h> #include <stdlib.h> #include <windows.h> #include <exception> #include <memory> #include " Scope.h" TScope global ; struct A1 { A1() { printf( "

A1 %x" , this ); } virtual ~A1() { printf( "

~A1 %x" , this ); } }; struct A2 { A2() { printf( "

A2 %x" , this ); } virtual ~A2() { printf( "

~A2 %x" , this ); } }; struct A3 { A3() { throw std::bad_alloc(); printf( "

A3 %x" , this ); } virtual ~A3() { printf( "

~A3 %x" , this ); } }; void Test() { try { Scope local; std::tr1::shared_ptr<A1> spA1( new A1); std::tr1::shared_ptr<A2> spA2( new A2); std::tr1::shared_ptr<A3> spA3( new A3); } catch (...) { printf( "

exception" ); } } void main() { Test(); }



At first let me comment out your allocation handlers and use tr1 version of shared_ptr and just simply run the test. The output is following



A1 635f68

A2 6322d0

~A2 6322d0

~A1 635f68

exception



what exactly corresponds to the standard (when an exception happens during a object construction - in our case the struct A3 - the memory is deallocated and no destructor of the corresponding class is called). I hope, that this simple explanation is clear (not only for you, but also to other members of codeproject).



Now I come to your allocation handler. Let me comment out shared pointers and use your allocation handlers.



void Test() { try { Scope local; // all objects associated with scope will be deallocated by leaving procedure A1* pA1 = new (local) A1; A2* pA2 = new (local) A2; A3* pA3 = new (local) A3; /* std::tr1::shared_ptr < A1 > spA1(new A1); std::tr1::shared_ptr < A2 > spA2(new A2); std::tr1::shared_ptr < A3 > spA3(new A3); */ } catch (...) { printf("

exception"); } }



The output after this modification is unfortunately different:



A1 625f68

A2 625fa8

~A3 622320 //ops, why this is here?

~A2 625fa8

~A1 625f68

exception



You can see that the destructor of A3 structure is called, which is against the standard. The reason of this behaviour can be found out in your operator delete, which is called immediately after a throwing inside A3 constructor.



inline void __cdecl operator delete(void* ptr,Scope& scope) { scope.del(ptr); ((Scope::Obj*)ptr)->~Obj(); free(ptr); }



You can argue, that my example code has a scope object inside the try block. Ok, let me modify the code of the test function in the way how you suggested in your article.



void Test() { Scope local; try { A1* pA1 = new (local) A1; A2* pA2 = new (local) A2; A3* pA3 = new (local) A3; } catch (...) { printf( "

exception" ); } }



Unfortunately the output after this modification is almost the same.



A1 2d5f68

A2 2d5fa8

~A3 2d2320 //ops, the A3 destructor is still called

exception

~A2 2d5fa8

~A1 2d5f68





Let me make a conclusion. If you want community to accept your solution, you have to provide the same exception handling in all aspects like generic smart pointers do. Anyway I can say, that your approach is an interesting study case, and personally I'm curious how far you can get with this simple design. I always like when someone brings an unconventional ideas in C++, because it can be a way to improve our skills. Good luck and surprise me.



modified 24-Oct-12 8:01am.







A1 2d5f68 A2 2d5fa8 exception ~A2 2d5fa8 ~A1 2d5f68



The example in zip was without it to show that cleanup works also without derive.



This part of the article needs to be improved. I will update samples and article right after this post



I guess I should remove the no derive alternative altogether and don't even mention it.

It will lead only to problems. Ie some newcomer on project can easily reorder virtual methods etc.



So. Thx for this important observation.

You are helping to make article better and code more usefull to people.

And I highly appreciate it.



"There is always a better way"







"Since we want Scope to cleanup everything for us no matter what exception happens we must keep scope declaration outside try catch statement (not within)."



There is no limitation where the Scope is placed in function. It may be placed also inside a try block. In such case we can get even a better order of objects' destructor. The same like for smart pointers.



I have tested only two cases:

- exception occurs after creation of A2

- exception occurs during construction of A3

For both I got the same output.



void Test() { try { Scope local; A1* pA1 = new (local) A1; A2* pA2 = new (local) A2; A3* pA3 = new (local) A3; } catch (...) { printf( "

exception" ); } }



Output:

A1 785f68

A2 785fa8

~A2 785fa8

~A1 785f68

exception





postpone cleanup of objects that didn't fail after exception handler can be useful.

Store valid results. etc.



I changed mentioned article text and added your observation as preffered way ( Scope within try )while explaining alternatives.



Hope it's better

"There is always a better way"







If you want to little bit improve the performance of locked version (no guaranty, it has to be tested) of Scope, have a look at this page (Benaphore version for Win32):



http://preshing.com/20120226/roll-your-own-lightweight-mutex[^]



Did you already think about the array version of new and delete operators? To fulfil for example following case:



{ Scope local; A1* aA1 = new A1[ 10 ]; delete [] aA1; A1* slA1 = new (local) A1[ 10 ]; }



And some devil case from theory:

The new operator cannot be used to allocate a function, but it can be used to allocate pointers to functions. The following example allocates and then frees an array of seven pointers to functions that return integers.



int (**p) () = new ( int (*[ 7 ]) ()); delete [] p;



Of course, this case is really out of scope of your article and limits of your solution (the required inheritance from Scope::Obj class). However, someone can replace a pointer to function with the pointer to a function object derived from your Scope::Obj class. Afterwards the only missing part is new implementation of operator new[] and operator delete[].



modified 25-Oct-12 14:03pm.



I definitely plan some benchmarks comparing to smart pointers so this can get interesting.



Yes I am planning adding subscript versions and realloc that i forgot before soon too. But I need to replace linked list from c version first to remove those allocs and add preallocate

"There is always a better way"



What about exception / thread safety of your Scope implementation?





"There is always a better way"



The biggest killer for me is that you are imposing inheritance on all classes that I want to use this. In many cases, I don't have the ability to modify the classes I'm going to use (i.e., external library classes). A smart pointer is a truly generic solution that isn't alien syntax, because it's so commonly used. shared_ptr has been in use for a decade in production code. It is a much cleaner solution.







The term "generic" in your claim of boost shared_ptr being generic solution for decade.



In C land smart-pointers simply doesn't offer any (nor "generic") solution at all and pretty much all lover level code like os/drivers are writen in it.

But C variant of scopes.h offers solution without need to inherit anything.



And in c++ If you can and wana rewrite any project by wrapping all ocurences of pointers and code using them to use stl/boost/whatever templates then yes you can call it "generic" solution.



If you can't than it is not "generic" solution for c++ as you said for decade.



There is large amount of source code around you that you decided to not see. Mainly os sources/drivers/embeded/or any c/c++ lib /c code.





What I don't understand looking in your profile thou is

how come that your account was created in year 2010

and zero everything just number of messages 1.

just to post this one downvote message.



Is this some new trend to pick one time use accounts to downvote ?

"There is always a better way"



modified 7-Oct-12 17:33pm.

