shared_ptr<> null deleter

I have just discovered to my delight that you can use shared_ptr with a null deleter to pass around stack based objects. This is great because a function shouldn't care whether a parameter passed to it is on the heap or stack. So I can write general functions to accept a shared_ptr and if I have to pass a stack based object I can just pass a null deleter. My question is whether the boost library has anywhere in it a null deleter object, or whether it is possible to define one in place with boost::lambda or similar? It seems silly that something that I imagine would be needed quite commonly is not included in the library. Thanks, Kevin Martin

Kevin Martin:
My question is whether the boost library has anywhere in it a null deleter object, or whether it is possible to define one in place with boost::lambda or similar?
boost::lambda::_1 should work as a null deleter, although you're right that we might wish to add boost::null_deleter. Can you please file a Trac ticket for this?

On Mon, May 12, 2008 at 8:26 PM, Peter Dimov
Kevin Martin:
My question is whether the boost library has anywhere in it a null deleter object, or whether it is possible to define one in place with boost::lambda or similar?
boost::lambda::_1 should work as a null deleter, although you're right that we might wish to add boost::null_deleter. Can you please file a Trac ticket for this?
Just a small note. It can be pretty expensive to use a shared_ptr for stack objects. shared_ptr allocates a shared_counter on heap and shared_ptr uses by default thread safe locking. If you can pass stack objects as shared_ptr instanses, why can't you pass them as references? Thanks, Ovanes

Kevin Martin wrote:
I have just discovered to my delight that you can use shared_ptr with a null deleter to pass around stack based objects. This is great because a function shouldn't care whether a parameter passed to it is on the heap or stack. So I can write general functions to accept a shared_ptr and if I have to pass a stack based object I can just pass a null deleter.
You can do a lot more than that! Consider the case of a handle of some sort that you need to return to a service when you are done. Case in point, in order to get rid of an sqlite database connection pointer you call db_close(), NOT delete. No big deal, just use db_close as your deleter! The fact that you can use anything as a deleter for a shared_ptr turns it into a hammer with a driver head. So then everything IS effectively a nail.

On 16 May 2008, at 16:49, Noah Roberts wrote:
You can do a lot more than that! Consider the case of a handle of some sort that you need to return to a service when you are done. Case in point, in order to get rid of an sqlite database connection pointer you call db_close(), NOT delete.
No big deal, just use db_close as your deleter!
Is encapsulating the sqlite pointer in an object and having the destructor do that not a better alternative? Other than the time and effort required to develop an encapsulating class I can't think of an example where it is better to do this. Except for stack/static objects. I'm pretty new to C++ though. Thanks, Kevin Martin

Kevin Martin wrote:
On 16 May 2008, at 16:49, Noah Roberts wrote:
You can do a lot more than that! Consider the case of a handle of some sort that you need to return to a service when you are done. Case in point, in order to get rid of an sqlite database connection pointer you call db_close(), NOT delete.
No big deal, just use db_close as your deleter!
Is encapsulating the sqlite pointer in an object and having the destructor do that not a better alternative?
Why would it be? What's gained?

On 16 May 2008, at 18:50, Noah Roberts wrote:
Kevin Martin wrote:
Is encapsulating the sqlite pointer in an object and having the destructor do that not a better alternative?
Why would it be? What's gained?
A layer of abstraction between the library calls and your user code. Whether this is a gain or not depends on your opinion I suppose. Thanks, Kevin Martin

Hi! Kevin Martin schrieb:
A layer of abstraction between the library calls and your user code.
Right. A layer of abstraction. That's just what I have: my abstraction does not expose any database header. Everything is hidden in a pimpl. But how shall I implement the pimpl? It needs to "own" a database connection. I could write a destructor that closes the connection. But I don't bother. I try to avoid writing any destructor. I stick to existant RAII classes. So I use a shared_ptr with a custom deleter that closes the connection. Feels safe. That's what I gain. :) Regards, Frank

Kevin Martin wrote:
I have just discovered to my delight that you can use shared_ptr with a null deleter to pass around stack based objects. This is great because a function shouldn't care whether a parameter passed to it is on the heap or stack. So I can write general functions to accept a shared_ptr and if I have to pass a stack based object I can just pass a null deleter.
I was recently torn between doing this or thinking whether I'm doing it wrong. I'm still not sure whether using shared pointers everywhere with null deleters for stack allocated objects is The Right Thing. It kind of defeats the purpose of shared_ptr which is to ensure that the lifetime of the pointer is guaranteed for you. With null deleters, maintenance programmers may do the wrong thing (stuff something in an event queue deep down, for example.) If anyone has any opinion (for or against) please shout! -- Sohail Somani http://uint32t.blogspot.com

Sohail Somani wrote:
I was recently torn between doing this or thinking whether I'm doing it wrong. I'm still not sure whether using shared pointers everywhere with null deleters for stack allocated objects is The Right Thing. It kind of defeats the purpose of shared_ptr which is to ensure that the lifetime of the pointer is guaranteed for you. With null deleters, maintenance programmers may do the wrong thing (stuff something in an event queue deep down, for example.)
If anyone has any opinion (for or against) please shout!
I certainly would not use them everywhere. That's just a waste as was expressed by another. The only time I think one would be justified in using a shared_ptr with a null deleter for a stack object is when passing one into a function that uses it. However, even this is dangerous since the fact that this function is using a shared_ptr indicates that it could make a copy of it! So you better be pretty darn sure of the lifetime of that shared_ptr so that it doesn't outlive your stack and get called somewhere down the line...pointing at stack area memory! You could return one to some internal data but this also seems like highly questionable design to me. All copies of that shared_ptr that you just handed out, pointing to your insides, better not outlive you...and you have 0 control over that. Putting an object into a shared_ptr is pretty much saying, "I don't care when this object is deleted but it better stick around long enough to be used by everyone that wants to do so." Stack objects don't fit this sentence at all.

I was recently torn between doing this or thinking whether I'm doing it wrong. I'm still not sure whether using shared pointers everywhere with null deleters for stack allocated objects is The Right Thing.
I think it depends how much you are relying on the smart pointer. If you are certain about the scope of the pointer - i.e. you know where the delete is going to happen and you are using the smart_ptr purely to guarantee delete is called properly then using a null deleter and stack based objects is fine. However, if you are using the smart_ptr to let objects run loose and clear up after themselves, then this absolutely shouldn't be used. My problem is module interfaces. I have a module (set of classes) that solve a problem, and they require data. They don't care whether that data is on the heap, the stack, or the moon. They just want a pointer to it. In this case it is arguable that the pointers passed shouldn't be smart pointers at all, just normal pointers. That is fine, but has two drawbacks: a) You have to call get() for all shared_ptr<>s everywhere instead of just passing the pointer b) You can't do this: void func1(int *x); boost::shared_ptr<int> func2(); func1(func2()); And to make matters worse, people might be tempted to do this! func1(func2().get()); By having func1 take a boost::shared_ptr<int> as argument, we can do this, but we can no longer pass stack objects (unless we can put a stack object in a shared_ptr)
It kind of defeats the purpose of shared_ptr which is to ensure that the lifetime of the pointer is guaranteed for you.
Just because the shared_ptr guarantees the lifetime of the pointer, doesn't mean you can forget about who has a copy of the pointer and why. If you are passing any kind of pointer/reference anywhere, then the callee should have a strict, well documented description of what it plans to do with that pointer, otherwise anyone may be modifying your object without you realizing. If you are having to think about this anyway, then you will know the lifetime of your pointer and whether you should pass it or not. I think using smart pointer without knowing both when and why it is being copied is inviting disaster, therefore I don't see the problem, but maybe someone can show me an example where this information can't/ shouldn't be known. Thanks, Kevin Martin

Kevin Martin wrote:
I was recently torn between doing this or thinking whether I'm doing it wrong. I'm still not sure whether using shared pointers everywhere with null deleters for stack allocated objects is The Right Thing.
I think it depends how much you are relying on the smart pointer.
If you are certain about the scope of the pointer - i.e. you know where the delete is going to happen and you are using the smart_ptr purely to guarantee delete is called properly then using a null deleter and stack based objects is fine. However, if you are using the smart_ptr to let objects run loose and clear up after themselves, then this absolutely shouldn't be used.
My problem is module interfaces. I have a module (set of classes) that solve a problem, and they require data. They don't care whether that data is on the heap, the stack, or the moon. They just want a pointer to it. In this case it is arguable that the pointers passed shouldn't be smart pointers at all, just normal pointers. That is fine, but has two drawbacks:
a) You have to call get() for all shared_ptr<>s everywhere instead of just passing the pointer b) You can't do this:
void func1(int *x); boost::shared_ptr<int> func2();
func1(func2());
And to make matters worse, people might be tempted to do this!
func1(func2().get());
By having func1 take a boost::shared_ptr<int> as argument, we can do this, but we can no longer pass stack objects (unless we can put a stack object in a shared_ptr)
As was mentioned by another, func1 seems to be poorly designed in that it is taking the wrong type of argument. 'x' should most certainly be a reference, not a pointer. This does not guarantee that nobody will do something as silly as taking the address of that object and keeping a pointer around, but it does advertise that it definitely should not be. If it does you can execute the developer that did it. Having fixed that, your call looks like this: func1(*func2()); Or the more sure: boost::shared_ptr<int> p = func2(); func1(*p); Now you're not assuming that p is not a temporary!

On 16 May 2008, at 18:43, Noah Roberts wrote:
Kevin Martin wrote:
And to make matters worse, people might be tempted to do this!
func1(func2().get());
Having fixed that, your call looks like this:
func1(*func2());
My understanding of how this works was wrong. I thought the object returned by func2 would go out of scope before the call to func1, meaning the pointer/reference passed to func1 would be hanging. It seems I am wrong though, at least with g++ and icc, so I guess I need to go back to C++ 101. func1() may be a member function of an object which needs to keep the argument somewhere, that is why I prefer to pass a pointer. Passing a reference gives the implication (at least to me) that the object is not going to keep any link with the argument after the call returns. Passing a pointer makes me think - hey, what is this object doing with this thing? Thanks, Kevin Martin

2008/5/16 Kevin Martin
func1() may be a member function of an object which needs to keep the argument somewhere, that is why I prefer to pass a pointer.
If it is keeping the argument, then it cares about ownership/lifetime, so one should pass a smart pointer to it.
Passing a reference gives the implication (at least to me) that the object is not going to keep any link with the argument after the call returns. Passing a pointer makes me think - hey, what is this object doing with this thing?
It doesn't convey that information to other readers of your code. All the language rules really give you is that passing by reference means that the variable must exist, while passing by pointer means that it could be NULL. -- Nevin ":-)" Liber mailto:nevin@eviloverlord.com (847) 691-1404

Kevin Martin wrote:
On 16 May 2008, at 18:43, Noah Roberts wrote:
Kevin Martin wrote:
And to make matters worse, people might be tempted to do this!
func1(func2().get());
Having fixed that, your call looks like this:
func1(*func2());
My understanding of how this works was wrong. I thought the object returned by func2 would go out of scope before the call to func1, meaning the pointer/reference passed to func1 would be hanging. It seems I am wrong though, at least with g++ and icc, so I guess I need to go back to C++ 101.
Actually, I think you are correct on that. A temporary will only survive into the function call if you are accepting a const& afaik. If you are accepting a const reference then whatever is passed in will be bound to that object and is guaranteed to survive. Otherwise no. Your shared_ptr code actually exhibits a serious issue then. The shared_ptr is not being bound to anything, only the return of get() is. So your shared_ptr will leave scope, be destroyed, and possibly leave your copy of the pointer returned by get() dangling into a deleted object. Note that not even accepting "const&" will help you here. The object pointed to by the shared_ptr is not a temporary and doesn't follow the rules of such wrt const references. So your shared_ptr could be the last one, go out of scope, delete the object returned by op*, and then your function is called with a reference to a deleted object - the worst of all possible worlds: impossible to defend against and incredibly difficult to debug. So you'd better be sure that your shared_ptr is not the last one! Better to keep a copy during the function call by assigning it to a local variable and THEN calling your function. Otherwise you are, effectively, depending on the internals of that function that returned the shared_ptr.

Hi! Noah Roberts schrieb:
Actually, I think you are correct on that. A temporary will only survive into the function call if you are accepting a const& afaik. If you are accepting a const reference then whatever is passed in will be bound to that object and is guaranteed to survive. Otherwise no.
All of my knowledge tells me: you are *not* in trouble here. But at least 10 years ago it was unclear, as this article from comp.std.c++ shows: http://groups.google.com/group/comp.std.c++/msg/3fbd47d85ab1e47d Regards, Frank

Frank Birbacher wrote:
Hi!
Noah Roberts schrieb:
Actually, I think you are correct on that. A temporary will only survive into the function call if you are accepting a const& afaik. If you are accepting a const reference then whatever is passed in will be bound to that object and is guaranteed to survive. Otherwise no.
All of my knowledge tells me: you are *not* in trouble here.
You're right. I don't know what I was thinking. Selective amnesia I guess.

2008/5/16 Kevin Martin
My problem is module interfaces. I have a module (set of classes) that solve a problem, and they require data. They don't care whether that data is on the heap, the stack, or the moon. They just want a pointer to it. In this case it is arguable that the pointers passed shouldn't be smart pointers at all, just normal pointers.
My general rule of thumb is that if mucking with ownership or lifetime, pass a smart pointer. If not, pass a raw pointer.
That is fine, but has two drawbacks:
a) You have to call get() for all shared_ptr<>s everywhere instead of just passing the pointer b) You can't do this:
void func1(int *x); boost::shared_ptr<int> func2();
func1(func2());
One could always add the following overload: inline void func1(shared_ptr<int> const& x) { func1(x.get()); }
And to make matters worse, people might be tempted to do this!
func1(func2().get());
What is wrong with that?
Just because the shared_ptr guarantees the lifetime of the pointer, doesn't mean you can forget about who has a copy of the pointer and why.
They are separate issues. There are times I need to know who has references to my object, other times it is sufficient to know how many outstanding references there are, and still other times where all I need to know is whether or not there are any outstanding references to my object.
If you are passing any kind of pointer/reference anywhere, then the callee should have a strict, well documented description of what it plans to do with that pointer, otherwise anyone may be modifying your object without you realizing.
Modifying your object is an orthogonal issue, better handled with judicious use of const.
I think using smart pointer without knowing both when and why it is being copied is inviting disaster, therefore I don't see the problem, but maybe someone can show me an example where this information can't/ shouldn't be known.
If you get a job at my company, I can show you a couple of examples. :-) Take your func2. It has no idea who will call it, so it can't possibly know why (or even if) its return value is being copied. Yet that is normal, not disasterous for things like factory functions. Another example: suppose I have a pool of expensive to construct cookies. My factory function returns them to some caller, and when the reference count goes to 0, I put the cookie back into the free pool. I don't know or care how many times the caller copies it; I just need to know when there are no outstanding references to it so that I can reuse it. Regards, -- Nevin ":-)" Liber mailto:nevin@eviloverlord.com (847) 691-1404

At 2:37 PM -0500 5/16/08, Nevin \":-]\" Liber wrote:
2008/5/16 Kevin Martin
: My problem is module interfaces. I have a module (set of classes) that solve a problem, and they require data. They don't care whether that data is on the heap, the stack, or the moon. They just want a pointer to it. In this case it is arguable that the pointers passed shouldn't be smart pointers at all, just normal pointers.
My general rule of thumb is that if mucking with ownership or lifetime, pass a smart pointer. If not, pass a raw pointer.
A brief addenda (from my rules of thumb) for non-ownership cases: * If the parameter is optional, pass a raw pointer, and the callee should expect a NULL pointer. * If the parameter is not optional, pass a reference. -- -- Marshall Marshall Clow Idio Software mailto:marshall@idio.com It is by caffeine alone I set my mind in motion. It is by the beans of Java that thoughts acquire speed, the hands acquire shaking, the shaking becomes a warning. It is by caffeine alone I set my mind in motion.

Marshall Clow wrote:
At 2:37 PM -0500 5/16/08, Nevin \":-]\" Liber wrote:
My problem is module interfaces. I have a module (set of classes) that solve a problem, and they require data. They don't care whether that data is on the heap, the stack, or the moon. They just want a pointer to it. In this case it is arguable that the pointers passed shouldn't be smart pointers at all, just normal pointers. My general rule of thumb is that if mucking with ownership or
2008/5/16 Kevin Martin
: lifetime, pass a smart pointer. If not, pass a raw pointer. A brief addenda (from my rules of thumb) for non-ownership cases: * If the parameter is optional, pass a raw pointer, and the callee should expect a NULL pointer. * If the parameter is not optional, pass a reference.
I don't like this idea. You are creating a dependency on the fact that the called function will NOT keep a copy - or you are insisting that the object in question implement the shared_from_this model. If you later decide that it would be prudent for the function or object in question to create or pass a kept copy of this object then you'll have to change the signature of the function and then each and every call to it (to remove get() calls). So while I can see some possible enforcement advantages to this in the rare case, I certainly wouldn't use it as a general rule of thumb. I can't count the number of times someone has assumed that nobody would need a copy of something and then requirements changing dictated that someone did.

At 3:13 PM -0700 5/16/08, Noah Roberts wrote:
Marshall Clow wrote:
At 2:37 PM -0500 5/16/08, Nevin \":-]\" Liber wrote:
My problem is module interfaces. I have a module (set of classes) that solve a problem, and they require data. They don't care whether that data is on the heap, the stack, or the moon. They just want a pointer to it. In this case it is arguable that the pointers passed shouldn't be smart pointers at all, just normal pointers. My general rule of thumb is that if mucking with ownership or
2008/5/16 Kevin Martin
: lifetime, pass a smart pointer. If not, pass a raw pointer. A brief addenda (from my rules of thumb) for non-ownership cases: * If the parameter is optional, pass a raw pointer, and the callee should expect a NULL pointer. * If the parameter is not optional, pass a reference.
I don't like this idea. You are creating a dependency on the fact that the called function will NOT keep a copy - or you are insisting that the object in question implement the shared_from_this model.
I explicitly qualified this approach with "for non-ownership" cases. As for the other point - I certainly am. When I write a routine that returns an std::auto_ptr<whatever> I am creating a dependency on the fact that the routine will allocate some memory and return it - and it is the responsibility of the caller to deal with that. Alternately, if I write a routine that takes an auto_ptr<whatever> as a value parameter, that means that I expect the callee to dispose of that memory. This is another way (sadly not enforced by the language; hence just a convention) to indicate parameter requirements.
If you later decide that it would be prudent for the function or object in question to create or pass a kept copy of this object then you'll have to change the signature of the function and then each and every call to it (to remove get() calls).
Yes. And since the semantics of the routine are changing, I have to examine each and every call to the routine anyway to determine if such a change will introduce bugs. I don't see that that is less work. The only difference between changing the semantics and changing the calling convention is that the compiler will help you if you change the calling conventions. -- -- Marshall Marshall Clow Idio Software mailto:marshall@idio.com It is by caffeine alone I set my mind in motion. It is by the beans of Java that thoughts acquire speed, the hands acquire shaking, the shaking becomes a warning. It is by caffeine alone I set my mind in motion.

2008/5/16 Noah Roberts
I don't like this idea. You are creating a dependency on the fact that the called function will NOT keep a copy - or you are insisting that the object in question implement the shared_from_this model. If you later decide that it would be prudent for the function or object in question to create or pass a kept copy of this object then you'll have to change the signature of the function and then each and every call to it (to remove get() calls).
If this were Java, I might agree. Given your assumption, I don't see how you can pass pointers to stack variables, pointers to member variables, pointers to objects in STL containers, etc., to any function, because it is not possible for those functions to know, let alone change, the lifetime of those objects. Do you really allocate every single variable in your program on the heap and store it in a shared_ptr, just in case? Regards, -- Nevin ":-)" Liber mailto:nevin@eviloverlord.com (847) 691-1404

Nevin ":-]" Liber wrote:
2008/5/16 Noah Roberts
mailto:roberts.noah@gmail.com>: I don't like this idea. You are creating a dependency on the fact that the called function will NOT keep a copy - or you are insisting that the object in question implement the shared_from_this model. If you later decide that it would be prudent for the function or object in question to create or pass a kept copy of this object then you'll have to change the signature of the function and then each and every call to it (to remove get() calls).
If this were Java, I might agree.
Given your assumption, I don't see how you can pass pointers to stack variables, pointers to member variables, pointers to objects in STL containers, etc., to any function, because it is not possible for those functions to know, let alone change, the lifetime of those objects.
Do you really allocate every single variable in your program on the heap and store it in a shared_ptr, just in case?
I can't think of what your reasoning is that leads you to this conclusion from my statements. Care to explain?

2008/5/16 Noah Roberts
Nevin ":-]" Liber wrote:
2008/5/16 Noah Roberts
mailto:roberts.noah@gmail.com>: I don't like this idea. You are creating a dependency on the fact that the called function will NOT keep a copy
Suppose I had the function: void foo(int const* p) { if (p) std::cout << *p << std::endl; } It is perfectly legal to call it as: void bar() { int i(2); foo(&i); } int main() { bar(); bar(); } Now, if we let foo squirrel away a copy of p, as in: void foo(int const* p) { static int const* pp; if (p && pp) std::cout << *pp << ' ' << *p << std::endl; pp = p; } The program is now broken, since it is illegal to dereference pp during the second call to foo(), yet foo has no way of knowing that. What should the interface to foo() be such that it doesn't break whether or not foo() keeps a copy of p? -- Nevin ":-)" Liber mailto:nevin@eviloverlord.com (847) 691-1404

Nevin ":-]" Liber wrote:
void foo(int const* p) { static int const* pp; if (p && pp) std::cout << *pp << ' ' << *p << std::endl; pp = p; }
What should the interface to foo() be such that it doesn't break whether or not foo() keeps a copy of p?
You might consider a shared_ptr. Seems like the perfect place for one.

2008/5/16 Noah Roberts
Nevin ":-]" Liber wrote:
void foo(int const* p) { static int const* pp; if (p && pp) std::cout << *pp << ' ' << *p << std::endl; pp = p; }
What should the interface to foo() be such that it doesn't break whether or not foo() keeps a copy of p?
You might consider a shared_ptr. Seems like the perfect place for one.
Let's recap: NL:If it is keeping the argument, then it cares about ownership/lifetime, so one should pass a smart pointer to it. MC:A brief addenda (from my rules of thumb) for non-ownership cases: * If the parameter is optional, pass a raw pointer, and the callee should expect a NULL pointer. * If the parameter is not optional, pass a reference NR:You are creating a dependency on the fact that the called function will NOT keep a copy NL:Do you really allocate every single variable in your program on the heap and store it in a shared_ptr, just in case? NR:I can't think of what your reasoning is that leads you to this conclusion from my statements. Care to explain? NL:What should the interface to foo() be such that it doesn't break whether or not foo() keeps a copy of p? NR:You might consider a shared_ptr. Seems like the perfect place for one. Please go back to the top and reread my rule of thumb (with Marshall's addendum, which I agree with and use). There is no safe, general way to have a function or class that takes a raw pointer or reference such that it can keep a copy of that pointer or reference and use it later. Someone outside of the function or class has to make external guarantees about its lifetime. If you have a function that doesn't keep a copy of a pointer or reference and then change its implementation so that it does, you are going to have to revisit every use of that function or class. In other words, code written like that is fragile and should be used sparingly. One of the few places I would use code like that is in the constructor of a smart pointer, where there are very well documented rules on how to call it. Almost everywhere else where lifetime/ownership matters past this one function call, I use a smart pointer instead of a raw one. I prefer not to have fragile code, where the caller has to "be careful" (which is merely a euphemism for "be perfect", as any mistake creates a subtle bug). If you a way of passing a raw pointer or reference where it is always safe for the called function to copy and use that pointer or reference later without any external guarantees about the lifetime of the object pointed/referred to, I'd certainly be interested in hearing about it. Regards, -- Nevin ":-)" Liber mailto:nevin@eviloverlord.com (847) 691-1404

Nevin ":-]" Liber wrote:
Please go back to the top and reread my rule of thumb (with Marshall's addendum, which I agree with and use).
You mean this: My general rule of thumb is that if mucking with ownership or lifetime, pass a smart pointer. If not, pass a raw pointer. Again, I disagree. Did you think I didn't read it and didn't know what I was disagreeing with? Marshall's addendum, to which I replied, also doesn't change it for me and relies, as the basis of its argument, on what I believe is questionable design decisions.
In other words, code written like that is fragile and should be used sparingly.
Exactly. That's why I disagree. I prefer not to have fragile code,
where the caller has to "be careful" (which is merely a euphemism for "be perfect", as any mistake creates a subtle bug).
Exactly why I disagree with your "rule of thumb".
If you a way of passing a raw pointer or reference where it is always safe for the called function to copy and use that pointer or reference later without any external guarantees about the lifetime of the object pointed/referred to, I'd certainly be interested in hearing about it.
The fact that there is none is why I disagree. At least a reference advertises the fact that you certainly should not be keeping a copy. So use a shared pointer or other RAII device when a reference is inappropriate...as a general rule of thumb. By using a pointer you are already, "mucking with ownership issues." Even if the answer to those issues is, "This function will not keep this pointer." So I think that your rule of thumb and the reasoning behind it are flawed. I can see when such is necessary given other constraints, for you'll never be able to get code perfect by all measures, but I do not like the idea of using it as a rule of thumb. If you see a pointer and you are not asking what the ownership semantics of that pointer are, I'd say you're neglecting to consider a potentially very large problem. Furthermore, your general rule of thumb is going to kick you in the face once you start mixing pointers gained and lost through other APIs such as wxWidgets, where passing a pointer into a function can be disowning it. When your general rule of thumb is, "Avoid raw pointers," you can separate that which you've created from these other libraries by simply documenting that any raw pointer, unless otherwise commented, is owned by that library. Your rule of thumb seems arbitrary to me, fails to address the ownership issue, and balances precariously upon the understanding of other developers in the face of competing paradigms. I don't think I like it.

Nevin ":-]" Liber wrote:
2008/5/16 Noah Roberts
: Nevin ":-]" Liber wrote:
2008/5/16 Noah Roberts
mailto:roberts.noah@gmail.com>: I don't like this idea. You are creating a dependency on the fact that the called function will NOT keep a copy
Suppose I had the function:
More to the point.
void foo(int const* p) { if (p) std::cout << *p << std::endl; }
I don't think foo in this case stands as an exemplary of good design choice. It should look more like so: void foo() { } void foo(int i) { std::cout << i << std::endl; } Overrides like this are one of the great features of C++. You might argue that foo looks more like: void foo(int * p = 0) // I see this a lot. { // ...bunch of stuff... *p = result of calculations // ... some cleanup maybe } Again, split it into two: int bunch_of_stuff() { ... } void cleanup() { ... } void foo() { bunch_of_stuff(); cleanup(); } void foo(int & p) { p = bunch_of_stuff(); cleanup(); } You'll notice that any time you could have the =0 foo you can use these two without any difference in calling semantics. In both you either call with an argument or don't. Only one case, and one you should never, ever do, is if you're passing around null pointers without caring. The point is that you've got a function that behaves differently based on a state variable being passed in. That may be a necessary evil but does not make for a good argument about best practice. I've happily kept my use of raw pointers to a bare minimum for quite a while now. Everyone on my team is more productive because of it. The difference here between using a pointer and splitting into two functions that use shared code but behave differently is really much more than just the difference between accepting pointer and reference as far as maintaining goes, but there's a lot there so I'm going to pass on that. What is important per this discussion is that since you are binding to the idea that you will not copy this data and hold it, make that as obvious as possible. Pointers are notoriously vague in their meaning and one always asks, "Is this function going to copy and hold this pointer?" Bind to as little as possible. Make clear any ambiguity. You've come up with a plan that works for you, but then somebody like me might come along and screw it all up. Who's fault is it, the one that didn't intuitively grasp the meaning of accepting raw pointers or the one that didn't clarify as much as possible by not doing so to begin with? If you really do need to do the whole if(p) thing, which is really smelly, then you might consider using a reference wrapper that is capable of being null. Make intentions clear, "this will not be copied," and leave the possibility of having an = 0 default. Pointers are necessary in some cases--you're not going to implement a vector without them--but really, in my opinion, should be avoided like the plague. You might be smart enough to use them without screwing up, I have to protect myself from myself. Now, again, there are cases when best practices go out the window in favor of other, more important criteria. If performance is really, really an issue then wrapping up every pointer you see is not exactly going to help you meet your criteria. So you sacrifice clarity for performance sometimes. But when you need to do this you know it...you've tested with a profiler and made your algorithm as efficient as possible and know that this shared_ptr is really, really causing you problems.

Sohail Somani wrote:
I was recently torn between doing this or thinking whether I'm doing it wrong. I'm still not sure whether using shared pointers everywhere with null deleters for stack allocated objects is The Right Thing. It kind of defeats the purpose of shared_ptr which is to ensure that the lifetime of the pointer is guaranteed for you. With null deleters, maintenance programmers may do the wrong thing (stuff something in an event queue deep down, for example.)
If anyone has any opinion (for or against) please shout!
I would say that this is a bad idea. It seems like there is an implicit contract when using shared_ptrs that the object won't be destroyed as long as anyone has a copy of that shared_ptr. (One way to advertise that copies of the smart pointer might not preserve the object could be to pass a weak_ptr instead of a shared_ptr. Unfortunately, the lock method of the weak_ptr could still cause problems because it returns a shared_ptr, and you could still end up with a stale pointer.) Overall, it seems like using a shared_ptr for stack objects is misleading, and as a result, may eventually lead to difficult to trace bugs. Unless allocating and deallocating the object is a bottleneck in your code, the potential costs outweigh the benefits. David

David Walthall wrote:
Overall, it seems like using a shared_ptr for stack objects is misleading, and as a result, may eventually lead to difficult to trace bugs. Unless allocating and deallocating the object is a bottleneck in your code, the potential costs outweigh the benefits.
Yes, this was my general conclusion (i.e., I didn't actually do it!) For freshly written code, I could not think of a case where I would suggest a null deleter but that doesn't mean that there isn't one. I think before adding a null deleter to Boost, it might be useful to think about the cases in which it is necessary and whether they occur enough to encourage its use. After all, if it is in Boost, chances are that it is a proper way to do things. -- Sohail Somani http://uint32t.blogspot.com

Hi! Kevin Martin schrieb:
I have just discovered to my delight that you can use shared_ptr with a null deleter to pass around stack based objects.
I got this idea recently, too. In short:
struct Bar;
struct Foo{
vector
participants (9)
-
David Walthall
-
Frank Birbacher
-
Kevin Martin
-
Marshall Clow
-
Nevin ":-]" Liber
-
Noah Roberts
-
Ovanes Markarian
-
Peter Dimov
-
Sohail Somani