|
|
Comments and Discussions
|
|
 |

|
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:57.
|
|
|
|

|
thank you for fact based discussion. I value it.
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:36.
|
|
|
|

|
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
|
|
|
|

|
Thx I appreciate it
"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"
|
|
|
|

|
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. 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. The RAII idiom scopes objects without releasing pointers to them.
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: 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:36.
|
|
|
|

|
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:02.
|
|
|
|

|
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("\n A %x",this);
}
virtual ~A()
{
printf("\n ~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!"
|
|
|
|

|
Thx for noticing this.
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:06.
|
|
|
|

|
When I was saying in my original comment that your article shows the strength of C++ and how it can be used or misused. I asked how you solve the exception handling. My purpose was to bring you to think a little bit more about your solution. I'm afraid, that your modification inside your Scope class destructor is not enough to consider your solution to be a generic one.
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("\n A1 %x",this);
}
virtual ~A1()
{
printf("\n ~A1 %x",this);
}
};
struct A2 { A2()
{
printf("\n A2 %x",this);
}
virtual ~A2()
{
printf("\n ~A2 %x",this);
}
};
struct A3 { A3()
{
throw std::bad_alloc();
printf("\n A3 %x",this);
}
virtual ~A3()
{
printf("\n ~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("\n 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("\n 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("\n 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:01.
|
|
|
|

|
Well the solution is simple. If you wanna have standard behavior (ie no destructor called) Use derive statement as advised in article.
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"
|
|
|
|

|
When you restrict that a type has to be derived from Scope::Obj class, the following text from your article can be modified:
"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("\n exception");
}
}
Output:
A1 785f68
A2 785fa8
~A2 785fa8
~A1 785f68
exception
|
|
|
|

|
Yup.
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"
|
|
|
|

|
Just only one interesting tip, one question and provocative case:
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:03.
|
|
|
|

|
Wow very interesting lock optimization. I love how this skips expensive trip to kernel in singlethreaded usage cases 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 thread safe version TScope in Scope.h if you need it.
"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.
|
|
|
|

|
Everytime I get downvote I try to findout what was the reason so article can be improved.
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:33.
|
|
|
|

|
You're funny. You're taking every criticism of your code as an attack against you. Oh well. Peace out dude. Also, before you decide to argue with someone over what generic code is, please do the research to find out that it has a very precising definition when used in relation to C++ code that your response shows you don't really understand.
|
|
|
|

|
Your idea would gain some more charm if you took the storage allocation from the stack instead of the heap. Microsoft uses the _alloca function to do that. This would combine the performance advantages of allocating from the stack with your principal idea.
Although I have to agree with most of the arguments in the "vote 2" thread, I voted 5 as kind of a compensation.
|
|
|
|

|
A good exercise but no real value as smart pointers are a better alternative starting with the fact that they don't require a virtual desctructor and also because they don't bring any undefined behavior.
|
|
|
|

|
Philippe Mori wrote: no real value
Hmm let's see .
Stl in current standard don't have any alternative to code I provided since auto_ptr can't be stored to containers. Ie you can't have automatically cleaned up array of dynamic objects as in article. This situation will take a looots of time until new versions of ide along with new versions of compillers become more widespread since only the new standard in top of the notch compillers supports unique_ptr that still don't work in containers (custom deleter). Penetration of boost with shared_ptr is even smaller then stl.
Requiring virtual destructor or deriving from Scope::Obj is no problem. Objects are yours afterall.
requiring boost/stl for simple pointer and not being able to do usable libs or dll's anymore problem is.
Plus what is your solution for situations where you can't use stl/boost? embedded/games/legacy/libs? Still no value ?
Now what you call undefined behavior is actually defined behavior known as preserving order of methods in vtable that's why you either derive from Scope::Obj or have first virtual method destructor the result is the same.
To sumarise deriving from Scope::Obj is very small price for what you gain and there is nothing undefined or questionable about it. And as for value it clearly has value in situations where you can't or don't wana use boost.
"There is always a better way"
modified 3 Oct '12 - 5:36.
|
|
|
|

|
If you cannot uses shared_ptr, then you can define your own simplified scoped_ptr as this:
template <typename T> class scoped_ptr {
public:
scoped_ptr(T *p_) : p(p_) { }
~scoped_ptr() { delete static_cast<T *>(p); }
private:
scoped_ptr(const scoped_ptr &); scoped_ptr &operator=(const scoped_ptr &); T *p;
};
and use like that:
{
A *a = new A;
scoped_ptr<A> xa(a);
a->do_something();
}
By the way,
Philippe Mori
modified 2 Oct '12 - 20:48.
|
|
|
|

|
Yes thats one solution too. Althou scoped_ptr is not very good choice since you will later actually use the objects even within scope.
A auto_ptr is a pointer with copy and with move semantics and ownership (=auto-delete).
A unique_ptr is a auto_ptr without copy but with move semantics.
A scoped_ptr is a auto_ptr without copy and without move semantics.
Whenever you want to explicitely have move semantics, use a unique_ptr.
Whenever you want to explicitely disallow move semantics, use a scoped_ptr.
All pointers allow swap semantics, like p.swap(q). To disallow those, use any const …_ptr.
There are situations, where you want to use a scoped_ptr pointing to one of several interchangeable objects: Because of the absence of move semantics, it is quite safe (in respect to obvious bugs) that it will not accidentally point to null because of an unintended move. Worth to mention: scoped_ptrs can still be swapped efficiently. To make it movable and/or copyable – but still with these swap semantics – you might want to consider using a shared_ptr pointing to a scoped_ptr pointing to an exchangeable (via scoped_ptr::swap) object.
If you wana have everything wrapped in nondebuggable templates and make your code hell lot less unerstandable and readable by lenghty template statements everywhere not mentioning performance. because you are replacing binops with calls everywhere.
While it's always good to know all alternatives with pluses and minuses.
bugs and lost performance(no move in your scoped_ptr) arise from copy paste of templates
without having and idea what code you created actially does.
"There is always a better way"
modified 3 Oct '12 - 6:04.
|
|
|
|

|
The latest VS debuggers make viewing templated classes easy. This was an issue VS 6.0, but the later versions, give us a full view of the object contained in the template.
Template calls are inlined whenever possible. I know not of these performance issues you speak of.
If you don't know what the code you create does, then you have other problems.
|
|
|
|

|
My code is a minimal sample that I have written in a few minutes mainly to show a minimal scoped pointer that could be used instead of a Scope if someone doesn't want to uses those from STL or boost.
For array, options are more discutables. It depends on the size of objects and the number of items and operations that will be done. I would typically use an array of object whenever it make sense...
I think that in case where you would need a Scope object (large array of pointers), I would probably put the algorithm in a class itstead of the function and then the destructor of the class would do the actual cleanup.
To be honest most of my development is done in C# now and almost all my C++ code has been converted so that it compile with /clr:safe. I have done a lot of C++ coding in the past and I was knowing the language quite good but I am not up-to-date with C++ 11.
Philippe Mori
|
|
|
|

|
Ok, just admit that smart pointers are the better way to go, and call it a day.
Philippe Mori is right, although I wouldn't have given you a 2, just to make the point.
|
|
|
|

|
Thx
"There is always a better way"
|
|
|
|

|
Well, the main reason that I gave that score is because I believe that the article is misleading. In particular, in the case of "scoped" pointer, almost any existing smart pointer can be used even your own like the sample I gave in another answer.
For example, the article mention that auto_ptr cannot be used in containers. While it is true, using a Scope for that is not an elegant solution as it severily limits how the container can be used (replacing and removing objects with or without deletions).
Also such Scope class encourage poorly designed code where a lot of pointer are used. Why not use objects on the stack whenever possible and have a class for the algorithm itself if it is complex and need a lot of pointers?
Finally, your way of doing things goes against the standard way of doing things thus a C++ expert will have to figure out what your class do to understand your code while he already knows standard pointers classes like shared_ptr.
By the way, even though old compilers does not support move semantic, you can do quite a lot with them. Just some operation will be a bit less efficient but so is your Scope object.
Philippe Mori
|
|
|
|

|
Philippe Mori wrote: Well, the main reason that I gave that score is because I believe that the article is misleading. In particular, in the case of "scoped" pointer, almost any existing smart pointer can be used
If you know anything about smart-pointers you would had known that pretty much all stl smart-pointers and there is lot of them are not usable in array I described in article. Tho only ones usable are just two in boost or c++ 11 that majority not yet use having valid licenses for old ides.
So much for your reason of voting 2
Philippe Mori wrote: Scope class encourage poorly designed code.where a lot of pointer are used.
Since when is "a lot of pointers" poorly designed code.
there is no faster alternative for sorting 1000 pointers to objects as in example.
Go ahead sort 1000 objects. There are no rules just guides. Ie you always need to use your brain not dogmas.
The Scope paradigm. Ie automatic cleanup when going out of scope is exacty the same concept you mark "encourages poorly designed code" when I use it while evangelizing it when you propose the same thing like in this yours sentence. Ie you contradict yourself.
Philippe Mori wrote: Why not use objects on the stack whenever possible
From your answers you seem to lack basic understanding of performance advantage of pointers hate scopes in one case while promoting them in another.
You lack basic understanding of security too. Doing objects and especially arrays of them on stack is
A) slower then on heap and severely restricted. Especially read about stack using VirtualAlloc
B) big security hole. stack is data interleaved with controlflow. Go read something about buffer overflows and main source of exploits.
You missed the whole purpose of comunity is helping each other not destroyng other peoples hard work for no rational reasons.
That being said you pretty much remind me of this tall guy
"There is always a better way"
|
|
|
|

|
I don't hate scope. I just hate the idea of being dependent on an undefined behavior (the cast to call a virtual destructor) and the fact your Scope object manage object lifetime independantly of the objects themselves which is error prone when not all objects are restricted to local scope.
That is when you use a shared_ptr, the object is responsible for a single pointer and you can reset it, transfer it, replace it... On the other hand, when using a Scope, objects are automatically added to the scope by operator new (and in fact, if there is an exception you also need placement delete to ensure that the pointer is removed from the Scope object to avoid double deletion).
I haven't consider the security aspect as in my case I develop specialized applications that should not be a target for those attacks and I almost always use std::vector and such as containers so I should not be too much affected by problems like buffer overflow (as the point to memory somewhere else).
Philippe Mori
|
|
|
|

|
Philippe Mori wrote: I just hate the idea of being dependent on an undefined behavior (the cast to call a virtual destructor)
Now enlighten me. How is deriving from Scope::Obj "undefined". One simple answer.
And if the answer is "no it is not undefined"
Then you can enlighten me again about what makes you "being dependent" on virtual destructor when whole article is clearly stated that deriving from Scope::Obj is another simple option.
Philippe Mori wrote: and the fact your Scope object manage object lifetime independantly of the objects themselves which
Yes Scope manages lifetime of objects exactly like C++ internal scope manages lifetime of static objects. Again you contradict yourself and keep running in circles.
Philippe Mori wrote: is error prone when not all objects are restricted to local scope.
Scope manages only objects that are within it's scope exactly like internal c++ scope no change there either. ie nothing is more eeror prone or less error prone than with standard c++ scope.
Philippe Mori wrote: (and in fact, if there is an exception you also need placement delete
No you dont. At least try to read the code you are atempting to bash. Destructors of scope is called and objects are simply deleted.
Philippe Mori wrote: develop specialized applications that should not be a target for those attacks
promoting unsafe design patterns because you rightnow doesn't seem to be target of attacks is just wrong.
Philippe Mori wrote: and I almost always use std::vector and such as containers so I should not be too much affected by problems like buffer overflow
Man... Did you meant that sentence seriously ? ;D
"There is always a better way"
|
|
|
|

|
For sure, there are things that you say I don't understand and they are thing that I say that you don't understand...
Effectively, deriving from Scope::Obj is not undefined behavior but your design don't enforce it as far as I understand. You would have to override new operator for Scope::Obj instead so that that overload cannot be used with object that does not derives from it.
After some testing, it seems that placement delete is not required in your case (and in fact I haven't figure out how to implement it) because your scope object will handle it.
http://en.wikipedia.org/wiki/Placement_syntax[^]
In simple cases and when uses as intended your Scope object will works... but in a big project it might be difficult to ensure it always properly used. One case that is better handled by C++ 11 is the case where some of the objects that were created should be transfered elsewhere. As long as objects are only added and systematically need to be deleted at the end of the scope, it should works.
Philippe Mori
|
|
|
|

|
Philippe Mori wrote: For sure, there are things that you say I don't understand and they are thing that I say that you don't understand...
Probably yes.;)
Philippe Mori wrote: You would have to override new operator for Scope::Obj instead so that that overload cannot be used with object that does not derives from it.
That's probably one of them. But hey it's too late here anyway ;D
Philippe Mori wrote: After some testing, it seems that placement delete is not required in your case (and in fact I haven't figure out how to implement it) because your scope object will handle it.
You can find placement delete in scope.h
it's used in rare cases(scopes are usually small) when you wana delete objects before leaving scope(ie you delete and remove them from scope linked list) so leaving scope will not attempt to deallocate them.
Philippe Mori wrote: One case that is better handled by C++ 11 is the case where some of the objects that were created should be transfered elsewhere.
Adding scope to class will release object resources only when object is explicitly deleted. ie allow object to be passed around as much as you like and destroying it only when needed for
example you pick task object someone else pushed to work queue. process task object and call delete on it. Internally all pointer related resources associated with internal object scope will get destroyed/freed as it would be the case with object having smartpointers as members.
That being said I don't enforce it on anyone.
If you like to use templates why not. use them.
This is just alternative solution.
Take care
"There is always a better way"
|
|
|
|

|
How is your work destroyed?
It's being criticized. If you decide to make software engineering your profession, then this is something you need to become comfortable with. You'll enjoy feedback from your peers for the rest of your career.
// Declarations
typedef std::shared_ptr tdMyObjectPtr;
std::map MyObjectMap;
// Add a sorted element... you can do this millions of times...
tdMyObjectPtr MyObjectPtr(new CMyObject);
MyObjectMap.insert(std::pair(CMyKey(MyObjectPtr), MyObjectPtr);
Shared pointers do have some overhead. I haven't found a situation on modern computer systems where the overhead created performance issues. The time saved in writing code makes it extremely worthwhile. I know exactly what this code does.
When you need breakneck speed, you'll avoid the heap the operations altogether, and work with fixed size buffers or static objects.
You do know that the heap is allocated the same way as the stack, right? Pages are not allocated until needed. Memory that you haven't allocated is virtualized. It isn't accessible. When a pointer in your code access a valid memory location that is on an unallocated page, the OS traps the exception, and then swaps a page of memory into that space. the heap works as a linked list and thus requires additional operations for housekeeping that are not needed on the stack. So you have it backwards, the stack is faster.
Yes the stack is used for exploits, but in this case you're talking about buffer overruns as an exploit. Simply creating objects on the stack does create a security hole. The exploits occur when you write memory past the bounds of an object.
|
|
|
|

|
JackDingler wrote: How is your work destroyed?
criticized is when you do something wrong and an argument is being raised.
downvoting article for claims that are simply not true is not being criticized.
JackDingler wrote: MyObjectMap.insert(std::pair(CMyKey(MyObjectPtr), MyObjectPtr);
if you need to sort known big dataset then
hashtable you created is inferior from performance point of view from simple preallocated pointer array. not mentioning memory and performance overhead.
JackDingler wrote: When you need breakneck speed, you'll avoid the heap the operations altogether, and work with fixed size buffers or static objects.
Please notice how much time it took to allocate static array in This benchmark and how much dynamic array. Spend some time contemplating internally why
JackDingler wrote: You do know that the heap is allocated the same way as the stack, right?
Yes I do. Every thread have small chunk of physical memory mapped to virtual address space by processor. ie separate Global descriptor table entry of protected mode.
Why separate ? so separate page granularity can be set and that it can grow downwards.
Now if you know how mapping of physical to virtual works you know how expensive operation this.
Thats why concept of heap was actually created. when this mapping is so expensive wy map large chunk upfront and just redistribute large chunks of virtual memory keeping track of them in linked lists.
Stack doesn't get preallocated on end of stack is one page marked with trap flag. causing very expensive switch from ring 3 to ring 0 and back often covering 30 calls covering sybnchronization delays or more just to allocate stupidly small 4k page similar to virtualalloc. Ie it happens all the time. There is nothing like virtualized memory to mentioned . there is either reserve or comit. by reserving you merely reserve range of addreses that system is giwing away to random threads of process address space. so you can allocate contiguos large chunk by multiple allocations in future. it will not make allocations in future faster only possible
You see now why stack is and allways will be slow and heap fast? It's like bringing expencive truck of food to city from a far by one piece per track or bringing or at large chunks less frequently and redistributing them locally fast upon request from shops.
There is meny forms of exploits. using stack is most popular one because of static arrays and sloppy programing practices like "nah nobody will exploit my code"
JackDingler wrote: Simply creating objects on the stack does create a security hole.
Yes it does.
JackDingler wrote: but in this case you're talking about buffer overruns as an exploit.
There is many ways direct exploits indirect exploits logic exploits rights elevations heap sraying exploits.
Placing data you receive from net on static object in stack where you have control flow in language like c++ with no bounds checking you are pretty much reason why antivirus companies exist
Siple rule is just don't store data on stack along with controlflow.
It's slow and dangerous.
"There is always a better way"
|
|
|
|

|
Just because memory locations are chained in the heap does not mean you don't have the same Ring 0 and Ring 3 bounce, when new pages are allocated. Whom ever told you that heap memory is allocated in a fundamentally different way than stack memory, taught you wrong.
And I'll bite, how do you write stackless code in C++?
Your scope objects are on the stack, as you point out, that makes them a gaping security hole.
You're being criticized because you are wrong.
|
|
|
|

|
JackDingler wrote: Just because memory locations are chained in the heap does not mean you don't have the same Ring 0 and Ring 3 bounce
No you dont' have this bounce after every page ;D
Thats the whole point of heap memory -> speed
JackDingler wrote: Whom ever told you that heap memory is allocated in a fundamentally different way than stack memory, taught you wrong.
Go ahead and study linux and windows sources you would be surprised
JackDingler wrote: And I'll bite, how do you write stackless code in C++?
no po point is no stackless program but not to preffer slower and unsafe storage place to store and work with data and arrays especially.
Just because somebody will clean after us. With scopes for heap you have now the same autocleanup advantages and bigger performance + bigger safety.
I said bigger. Because you know there are heap exploits too. but those are several orders harder to do and stem mostly from badly written code doing manual cleanup deallocating comething twice overwriting valid memory. "the mentioned heap linked list" etc-> heap spraying.
Another reason to have automatic cleanup for heap.
Wether you prefer templates or not all alternatives need to be known and being worked on so we all have bigger amount of choices.
"There is always a better way"
|
|
|
|

|
Ladislav Nevery wrote: No you dont' have this bounce after every page ;D
Thats the whole point of heap memory -> speed
Where do you think the 4k pages that make up the heap is mapped on come from?
If you watch memory usage for a process, you'll see that they aren't all using 2gb each on a 32 bit processor. This is because pages aren't allocated until they are needed by the heap.
When you allocate memory from the heap (current models), it will attempt to find freed memory that will satisfy the requirement. If this isn't possible, then the heap will be expanded to satisfy the requirement. To do so, a ring 0 trap is triggered on the memory location, and the kernel will map memory into the processes heap space.
When this occurs, you'll see the memory allocated to the process grow.
If the memory in this page is later freed, then the page is returned and a ring 0 event is called to remove the page.
When this occurs, you'll see the memory allocated to the process shrink.
Expansion and shrinking of the heap and stack work from the same mechanisms.
I think you're confusing heap allocation with page management. The heap tracks memory in two linked lists. One linked list tracks allocated memory and another tracks unallocated memory. This mechanism rides on top of the OS's paging mechanism.
If you were right that heap memory doesn't get paged in and out, then there would be no page file and every process would have a fixed and unchanging quantity of memory. You'd have to set the heap size at compile time, like we used to in the old days before virtual memory was available on PCs.
|
|
|
|

|
JackDingler wrote: Ladislav Nevery wrote: No you dont' have this bounce after every page ;D
Thats the whole point of heap memory -> speed
Where do you think the 4k pages that make up the heap is mapped on come from?
Point of the sentence was that it's not mapped in smallest possible chunks = 4k but usually 1-8mb or more. thus loose overhead and gain speed
JackDingler wrote: If you watch memory usage for a process, you'll see that they aren't all using 2gb each on a 32 bit processor.
using 2gb of what ? physical? virtual? Where did I said that ?
JackDingler wrote: This is because pages aren't allocated until they are needed by the heap.
That is simply not true.
the moment the process runs even before entry point is reached usually 8 mb of heap =physical memory is mapped(reserve+commit) to virtual address space of every process. That's a lot of pages allocated before they are needed. if you put this in your linker settings /HEAP:1500,1500
and run you run your exe your exe will allocate 1.5 gb heap instead of default 8mb one. ie it will have extremely fast malloc for obvious price off course.
Quite opposite of what you claim. You see? The speed in malloc or new is gained by eating from this already allocated memory ie no slow physical mapping page after page or ring 0 roundtrip and back. You just make logical hole in your linked list noting down which chunk is in use. making this preallocated chunk fragmented. the whole physical mapping (VirtualAlloc) kicks in again when you exhaust whole 8mb (or whatever you set in linker settinks for exe) and for obvious performance reasons another big chunk is allocated at once(ie no 4k page like in stack case)
From cpu clock point of view it is rare event and pretty much happens every million years.
JackDingler wrote: If the memory in this page is later freed, then the page is returned and a ring 0 event is called to remove the page.
when you call free() / delete for usuall small chunks in c++ ie heap functions. no ring 0 event returns any page.
It just grows. Why? because of performance lost by unmapping/mapping it's simply not worth it.
only when your process workingset actually grows too big and it runs out of
physical memory to map into your process.
Only then It will simply page physical pages to pagefile and unmap it from virtuall memory. but when you touch them he maps them back trying to page out some other pages with older Touch date timestamps.
JackDingler wrote: Expansion and shrinking of the heap and stack work from the same mechanisms.
No. stack never shrinks. Heap pretty much never shrinks too.
Ie it is very conservetive when it comes to shrinking. Since it expects alloc will follow free. so doing expensive unmap/map is delayed as much as possible.
Small memory allocations that are free'ed to the heap are usually placed into a list that is used for fast allocations.
Even without this optimization, the heap mamager is free to hold into the heap bucket from which the allocation was made. In order for memory to be returned to the system (VirtualFree'ed) all blocks in a 64KB block must be free'ed and combined by the heap manager.
And as for growing. Heap grows by malloc/new/etc. stack grows by triggering guard pages at their end and only in very limited range. usually less then few mb.
JackDingler wrote: I think you're confusing heap allocation with page management. The heap tracks memory in two linked lists. One linked list tracks allocated memory and another tracks unallocated memory. This mechanism rides on top of the OS's paging mechanism.
I will try to explain this again
If you map physical pages in "one page" requests to kernel and back like stack grow mechanism does. it will be slow to allocate anything bigger than 4k on stack becaouse all those trips to ring 0 add together as delay that accumulates with every such trip.
It's much more efficient to just allocate contiguous 8mb worth of pages by one trip to ring 0 as heap manager on exe start does and don't call ring 0 or mapping until it's exhausted.
Did you finally understood why is heap faster then stack and not oposite as you claim?
If yo still don't think so then explaint those numbers.
Why is allocating same sized static array slower then on heap.
www.codeproject.com/Articles/453022/The-new-Cplusplus-11-rvalue-reference-and-why-you#benchmark_results
JackDingler wrote: If you were right that heap memory doesn't get paged in and out,
Where did I said that ?
JackDingler wrote: You'd have to set the heap size at compile time, like we used to in the old days before virtual memory was available on PCs.
No go and check this linker setting /HEAP:reserve,commit does
Every exe on windows linux etc. In windows case there is field in PE file containing how big the should the process heap be before entering entry point.
"There is always a better way"
|
|
|
|

|
Your solution require discipline as the compiler cannot ensure that all required points are met (like having a virtual destructor).
I think it will also broke with multiple inheritances and it is fragile. For example, someone might remove the destructor to improve the efficiency of a class when refactoring code and then boom the application crash.
By using your solution, an application become less type-safe as the compiler will not be able to catch errors or do appropriate action as it would be the case with shared_ptr for example.
Philippe Mori
|
|
|
|

|
Philippe Mori wrote: Your solution require discipline
What code doesn't.
breaking stuff is part of refactoring. I can't count how many times I found comments don't touch this or don't try to optimize. That being said nothing holds you from deriving if you have problem keeping comment why virtual destructor is first virtual method.
Now you are Saying stuff like fragile or will somehow break. Yet I don't see code examples.
"There is always a better way"
|
|
|
|

|
As much as I could, I try to rely of the compiler instead of the programmer.
For example, any smart pointer will properly handle classes without virtual desctructor thus it is one less thing that the programmer has to be aware of.
I think it help to make robust code that works correctly the first time when it is not possible to do it the wrong way. By the way, it can be very hard to debug if Scope is used and a virtual destructor is missing in a complex function as crashes are not always immediate...
Philippe Mori
|
|
|
|

|
Ladislav Nevery wrote: Stl in current standard don't have any alternative to code I provided since auto_ptr can't be stored to containers
FYI: std::shared_ptr<>[^] works very well with containers, while std::auto_ptr<>[^] has been deprecated.
|
|
|
|

|
Hi Espen. Nice seeing you .How you doing.
Yes it's stated in article along with "shared_ptr. requires c++ 11 ".
Most people work with compillers they have licenses for. Ie don't expect it's widespread usage anytime soon keeping it in "one day maybe today boost or our own solution" shelf.
the same goes for unique_ptr.
But what you thing about the build in smart pointer idea I play with in clang?
Object*~
It seems it would simplify and solve whole lot of things.
Please don't answer "solved on library level or I will jump out of window "
"There is always a better way"
|
|
|
|

|
There are two instances of undefined behavior in the code snippets
1) You are allocating with malloc and using delete
2) You are casting an object to another type that is not void* and not a base class. Further you are calling a virtual function (virtual destructor) on that object.
|
|
|
|
 |
|
|
General News Suggestion Question Bug Answer Joke Rant Admin
|
reduce bugs and memory leaks by using new(local)
| Type | Article |
| Licence | CPOL |
| Version |
95 |
| First Posted | 29 Sep 2012 |
| Views | 25,189 |
| Downloads | 307 |
| Bookmarked | 18 times |
|
|