Re: [boost] scoped_ptr deleter

Stewart, Robert wrote:
That change creates multiple, distinct classes. shared_ptr<T,boost::checked_deleter<T>> is not the same as shared_ptr<T,my_deleter<T>>. By embedding the deleter in the shared_ptr with type erasure, shared_ptr<T> is shared_ptr<T> is shared_ptr<T>, regardless of the deleter.
Sure and that's the point :) As I wrote, since the "noncopyable nature" of scoped_ptr (shared_ptr is copiable by design), the creation of distinct classes isn't a problem. I think that the "real problem" here is to provide a solution that doesn't require extra memory overhead. _________________________________________________________________ Gossip, Sport, Notizie... Accendi la Messenger TV! http://www.messenger.it/messengerTV.aspx

Berserker wrote:
Stewart, Robert wrote:
That change creates multiple, distinct classes. shared_ptr<T,boost::checked_deleter<T>> is not the same as shared_ptr<T,my_deleter<T>>. By embedding the deleter in the shared_ptr with type erasure, shared_ptr<T> is shared_ptr<T> is shared_ptr<T>, regardless of the deleter.
Sure and that's the point :)
I'm sorry, I read your post too quickly, seeing "shared_ptr" where you wrote "scoped_ptr."
As I wrote, since the "noncopyable nature" of scoped_ptr (shared_ptr is copiable by design), the creation of distinct classes isn't a problem.
Right.
I think that the "real problem" here is to provide a solution that doesn't require extra memory overhead.
That, of course, requires nothing more than a static member function in the supplied deleter type as your proposal implies. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Berserker wrote:
Stewart, Robert wrote:
That change creates multiple, distinct classes. shared_ptr<T,boost::checked_deleter<T>> is not the same as shared_ptr<T,my_deleter<T>>. By embedding the deleter in the shared_ptr with type erasure, shared_ptr<T> is shared_ptr<T> is shared_ptr<T>, regardless of the deleter.
Sure and that's the point :) As I wrote, since the "noncopyable nature" of scoped_ptr (shared_ptr is copiable by design), the creation of distinct classes isn't a problem. I think that the "real problem" here is to provide a solution that doesn't require extra memory overhead.
Noncopyable nature doesn't mean its type is insignificant. This change would break the interface as the following code won't work anymore: template< template< typename > class Ptr > void make_a(Ptr< A >& p); scoped_ptr< A > p; make_a(p); This may not be the most frequent use pattern but nonetheless it's valid. I'd opt for this change if it wouldn't affect the interface in the breaking way. Otherwise, I like the way it is.

Andrey Semashev wrote:
Noncopyable nature doesn't mean its type is insignificant. This change would break the interface as the following code won't work anymore:
template< template< typename > class Ptr > void make_a(Ptr< A >& p);
scoped_ptr< A > p; make_a(p);
This may not be the most frequent use pattern but nonetheless it's valid.
I'd opt for this change if it wouldn't affect the interface in the breaking way. Otherwise, I like the way it is.
The difficulty that your example demonstrates is caused by the nature of template template parameters. scoped_ptr should not be blamed for that. BR, Dmitry

The difficulty that your example demonstrates is caused by the nature of template template parameters. scoped_ptr should not be blamed for
Dmitry Goncharov wrote: that. I agree, moreover I think that the advantages of this patch (often required in the list) justify the 1% use cases of Andrey Semashev's sample that in my opinion can be simplified in this way: template<typename T, typename P> void make_ptr(P &p) { p.reset(new T()); }

Berserker wrote:
Dmitry Goncharov wrote:
Andrey Semashev wrote:
Noncopyable nature doesn't mean its type is insignificant. This change would break the interface as the following code won't work anymore:
template< template< typename > class Ptr > void make_a(Ptr< A >& p);
scoped_ptr< A > p; make_a(p);
This may not be the most frequent use pattern but nonetheless it's valid.
The difficulty that your example demonstrates is caused by the nature of template template parameters. scoped_ptr should not be blamed for that.
I agree, moreover I think that the advantages of this patch (often required in the list) justify the 1% use cases of Andrey Semashev's sample that in my opinion can be simplified in this way:
template<typename T, typename P> void make_ptr(P &p) { p.reset(new T()); }
I think you meant "p.reset(new T);". Andrey's example is clearly unusual, but is not likely to be as trivial as you show (if you actually meant to illustrate Andrey's implied implementation). Clearly, make_a() can do any amount of work to create the pointer to put into p. I'd expect most use cases to be satisfied with a helper function that returns a raw pointer that it used to initialize a scoped_ptr rather than this approach, but there may be a non-obvious reason for it. Your version is as easy to use and provides the same flexibility, so it is an easy change to make and loses no functionality. Specialization is slightly more verbose, of course, but that shouldn't be a big deal either. Therefore, it is reasonable to break what little code follows Andrey's approach. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Am Wednesday 16 September 2009 10:34:35 schrieb Berserker:
Dmitry Goncharov wrote:
The difficulty that your example demonstrates is caused by the nature of template template parameters. scoped_ptr should not be blamed for
that.
I agree, moreover I think that the advantages of this patch (often required in the list)
could you explain what those advantages are? a scoped_ptr can't be copied at can't be moved. so there are two use cases left for a scoped_ptr deleter: - scoped_ptr as a class member: execute code when the class gets destructed -> that's what a destructor is for. - scoped_ptr as a local variable, execute code when something goes out of scope -> that's what boost ScopeExit is for. what is the use case that only a scoped_ptr deleter can handle?

On Wed, Sep 16, 2009 at 12:54 PM, Stefan Strasser <strasser@uni-bremen.de> wrote:
Am Wednesday 16 September 2009 10:34:35 schrieb Berserker:
Dmitry Goncharov wrote: > The difficulty that your example demonstrates is caused by the nature > of template template parameters. scoped_ptr should not be blamed for
that.
I agree, moreover I think that the advantages of this patch (often required in the list)
could you explain what those advantages are? a scoped_ptr can't be copied at can't be moved. so there are two use cases left for a scoped_ptr deleter:
- scoped_ptr as a class member: execute code when the class gets destructed -> that's what a destructor is for.
You could say the same for use delete in the destructor and never use scoped_ptr at all.
- scoped_ptr as a local variable, execute code when something goes out of scope -> that's what boost ScopeExit is for.
The same is valid for delete as well.
what is the use case that only a scoped_ptr deleter can handle?
None. The same is valid for almost anything else in RAII. But RAII simplifies things *a lot*. -- Felipe Magno de Almeida

Am Wednesday 16 September 2009 16:01:00 schrieb Felipe Magno de Almeida:
On Wed, Sep 16, 2009 at 12:54 PM, Stefan Strasser
<strasser@uni-bremen.de> wrote:
Am Wednesday 16 September 2009 10:34:35 schrieb Berserker:
Dmitry Goncharov wrote: > The difficulty that your example demonstrates is caused by the nature > of template template parameters. scoped_ptr should not be blamed for
that.
I agree, moreover I think that the advantages of this patch (often required in the list)
could you explain what those advantages are? a scoped_ptr can't be copied at can't be moved. so there are two use cases left for a scoped_ptr deleter:
- scoped_ptr as a class member: execute code when the class gets destructed -> that's what a destructor is for.
You could say the same for use delete in the destructor and never use scoped_ptr at all.
the point of a deleter is to execute CUSTOM code at destruction. using scoped_ptr (without deleter) automates deletion so you don't have to write custom code that does that. that simplifies things a lot. but a scoped_ptr delete would provide the same thing as a destructor or a scope exit: execute custom code. the only reason shared_ptr or unique_ptr do have deleters is because the point of destruction aren't statically known, as they can be copied or moved.

On Wed, Sep 16, 2009 at 1:08 PM, Stefan Strasser <strasser@uni-bremen.de> wrote:
Am Wednesday 16 September 2009 16:01:00 schrieb Felipe Magno de Almeida:
[snip]
the point of a deleter is to execute CUSTOM code at destruction.
Or custom desallocation. Or anything else.
using scoped_ptr (without deleter) automates deletion so you don't have to write custom code that does that.
So that you don't have to write anything actually. The same is true for custom destruction, the custom destruction can be shared through the application for use with allocators for example.
that simplifies things a lot.
It does for delete and it does for custom deleters.
but a scoped_ptr delete would provide the same thing as a destructor or a scope exit: execute custom code.
I don't understand why you think calling delete is so different from any other kind of destruction. To me it is as custom as anything else.
the only reason shared_ptr or unique_ptr do have deleters is because the point of destruction aren't statically known, as they can be copied or moved.
They do because it allows using other destruction strategy. Because the point of destruction is unknown is because it uses a type erasure technique. -- Felipe Magno de Almeida

Am Wednesday 16 September 2009 16:15:11 schrieb Felipe Magno de Almeida:
On Wed, Sep 16, 2009 at 1:08 PM, Stefan Strasser <strasser@uni-bremen.de> wrote:
Am Wednesday 16 September 2009 16:01:00 schrieb Felipe Magno de Almeida:
[snip]
the custom destruction can be shared through the application for use with allocators for example.
that's a valid point, but I'm still not sure if changing the type of a widely used class whose ONLY purpose is to call "delete", and break some code, is the right thing to do. it would be a new pointer type. the only thing it has in common with scoped_ptr is the pointer semantics. and you could easily introduce a new type that makes this easy to build: template<class T, class This> class ptr; //part of boost template<class T> struct ogre_ptr : public ptr<T,ogre_ptr<This> >{ ogre_ptr(T *t) : ... ~ogre_ptr(){ delete ogre-style. } };
that simplifies things a lot.
It does for delete and it does for custom deleters.
but a scoped_ptr delete would provide the same thing as a destructor or a scope exit: execute custom code.
I don't understand why you think calling delete is so different from any other kind of destruction. To me it is as custom as anything else.
the only reason shared_ptr or unique_ptr do have deleters is because the point of destruction aren't statically known, as they can be copied or moved.
They do because it allows using other destruction strategy. Because the point of destruction is unknown is because it uses a type erasure technique.

I've been lurking on this thread for awhile now, and as a person who is very familiar with the design and use of single-ownership smart pointer with custom-deleter capability, I figured it was time to throw my two cents in... I find custom deleters handy with single-ownership even when I'm not trying to move them out of scope. I usually use: const unique_ptr<MyClass, MyDeleter<A>> p(...); The const qualifier on the unique_ptr renders the unique_ptr to be practically the same thing as scoped_ptr. It will take a const_cast for this animal to transfer the ownership out of the scope. I've also found that sometimes I'll start with a same-scope use, but during refactoring, learn that I want to move the smart pointer creation into a factory function (for reuse in several places). When I do that, it is handy to not have to change smart pointer types (besides dropping the const qualifier if I used that). I've also found support for statefull deleters quite necessary (as opposed to just handling stateless deleters). The proposed code: template<class T, class D = boost::checked_deleter<T> > // first patch line class scoped_ptr { // ... public: ~scoped_ptr() { // ... deleter()(ptr); // second patch line, instead of boost::checked_delete } }; does not handle statefull deleters. If this community does decide to put a custom deleter into scoped_ptr (and I am ambivalent on that decision), then I strongly encourage the designers of this patch to mimic the custom deleter of unique_ptr. The latest specification of it can be found here: http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2009/n2914.pdf (search for "[unique.ptr]") And here is a more readable link in tutorial form which hits the highlights: http://home.roadrunner.com/~hinnant/unique_ptr03.html (and includes a C++03 emulation). The basic layout would look like: template<class T, class D = boost::checked_deleter<T> > // checked_deleter<T[]> should be partially specialized to handle arrays class scoped_ptr { typedef typename details::compute_pointer<T, D>::type pointer; tuple<pointer, D> ptr_; ... }; Until tuple is rewritten with the empty member optimization, you'll want to use compressed_pair<pointer, D> instead. The "compute_pointer" business defaults to T* unless D::pointer exists, in which case pointer is D::pointer. This supports putting this smart pointer in shared memory. Allocators, which are a common use case, often have this nested type, and sometimes, it is not a raw pointer. Storing the D as shown above will: 1. Optimize away the space for it when D is empty. 2. Support function pointer types. 3. Support non-empty (statefull) Ds. I also recommend supporting lvalue-reference D types. This is very handy when dealing with a stateful D that you don't want to move or copy (say because it is a data member of the enclosing class). Such support will probably come for free in C++0x, but requires a little extra work in C++03 to avoid the reference-to-reference situation. I also recommend deleter accessors which return *references* to the deleter: D& get_deleter(); const D& get_deleter() const; It is not uncommon to need access to the actual stored deleter (not a copy of it) during the lifetime of the smart pointer (I needed this just yesterday in code I was writing). This is used to change the state of the deleter which alters the behavior the deallocation process (e.g. do (or don't) destruct a certain field in the held object). And you'll want scoped_ptr constructors which accept a deleter (to initialize its state). And you'll want to disable the constructors which don't take a deleter when D is a pointer or reference type (lest it be null). scoped_ptr<A[]> could be a nice synonym for scoped_array<A> if checked_deleter is specialized for T[]. It is nothing more (or less) than a nice, easy to remember name for the array variant of the smart pointer. When you get done, you will have reinvented all of the const parts of unique_ptr (which has several years of refinement under its belt by now). I don't necessarily discourage that as it is a very educational exercise. -Howard

On Wed, Sep 16, 2009 at 11:47 AM, Howard Hinnant <howard.hinnant@gmail.com> wrote:
const unique_ptr<MyClass, MyDeleter<A>> p(...);
scoped_ptr<MyClass, MyDeleter<A>> p(...);
-Howard
If you had the scoped_ptr as you described, which would you then use - const unique_ptr or scoped_ptr? My first thought is that scoped_ptr is better named, making it easier to understand for readers/maintainers. ? Tony

On Sep 16, 2009, at 12:10 PM, Gottlob Frege wrote:
On Wed, Sep 16, 2009 at 11:47 AM, Howard Hinnant <howard.hinnant@gmail.com> wrote:
const unique_ptr<MyClass, MyDeleter<A>> p(...);
scoped_ptr<MyClass, MyDeleter<A>> p(...);
-Howard
If you had the scoped_ptr as you described, which would you then use - const unique_ptr or scoped_ptr? My first thought is that scoped_ptr is better named, making it easier to understand for readers/maintainers. ?
I would have a slight preference for const unique_ptr because it doesn't have swap or reset. It is *really* const. Beyond that it probably wouldn't matter much as I find myself typedefing most of my uses to something more descriptive within the context I'm using it, and easier to type. E.g.: typedef const unique_ptr<node_pointer, node_allocator> node_holder; -Howard

Howard Hinnant wrote: [snip]
If this community does decide to put a custom deleter into scoped_ptr (and I am ambivalent on that decision), then I strongly encourage the designers of this patch to mimic the custom deleter of unique_ptr.
[snip]
I also recommend supporting lvalue-reference D types.
[snip]
I also recommend deleter accessors which return *references* to the deleter:
D& get_deleter(); const D& get_deleter() const;
It is not uncommon to need access to the actual stored deleter (not a copy of it) during the lifetime of the smart pointer (I needed this just yesterday in code I was writing). This is used to change the state of the deleter which alters the behavior the deallocation process (e.g. do (or don't) destruct a certain field in the held object).
And you'll want scoped_ptr constructors which accept a deleter (to initialize its state). And you'll want to disable the constructors which don't take a deleter when D is a pointer or reference type (lest it be null).
scoped_ptr<A[]> could be a nice synonym for scoped_array<A> if checked_deleter is specialized for T[]. It is nothing more (or less) than a nice, easy to remember name for the array variant of the smart pointer.
When you get done, you will have reinvented all of the const parts of unique_ptr (which has several years of refinement under its belt by now). I don't necessarily discourage that as it is a very educational exercise.
99% agree with this. 1% is that I'd rather discourage to touch scoped_ptr and develop a new pointer type (perhaps, unique_ptr).

Stefan Strasser wrote:
the point of a deleter is to execute CUSTOM code at destruction. using scoped_ptr (without deleter) automates deletion so you don't have to write custom code that does that. that simplifies things a lot. but a scoped_ptr delete would provide the same thing as a destructor or a scope exit: execute custom code. the only reason shared_ptr or unique_ptr do have deleters is because the point of destruction aren't statically known, as they can be copied or moved.
My problem is simple: I have a custom memory manager (something similar to this one http://www.ogre3d.org/docs/api/html/OgreMemoryAllocatorConfig_8h.html ) and when the scoped_ptr goes out of scope I need to invoke my custom deleter instead of the classic "delete".

Berserker wrote:
Stefan Strasser wrote:
the point of a deleter is to execute CUSTOM code at destruction. using scoped_ptr (without deleter) automates deletion so you don't have to write custom code that does that. that simplifies things a lot. but a scoped_ptr delete would provide the same thing as a destructor or a scope exit: execute custom code. the only reason shared_ptr or unique_ptr do have deleters is because the point of destruction aren't statically known, as they can be copied or moved.
My problem is simple: I have a custom memory manager (something similar to this one http://www.ogre3d.org/docs/api/html/OgreMemoryAllocatorConfig_8h.html ) and when the scoped_ptr goes out of scope I need to invoke my custom deleter instead of the classic "delete".
Why not simply override operator delete? The benefit of extending scoped_ptr is when you want to tweak memory management for a subset of objects of the same type, which is quite rare, IMO. It is so rare that it deserves its own pointer type. Most often you'll just want to allocate all objects of the type the same way. That's from my experience. Another use case would be to use scoped_ptr as a scope guard releasing some resource, such as FILE*. But I tend to think that traditional scope guards and BOOST_SCOPE_EXIT are better tools for this.

Stefan Strasser wrote:
Am Wednesday 16 September 2009 10:34:35 schrieb Berserker:
I agree, moreover I think that the advantages of this patch (often required in the list)
could you explain what those advantages are?
To use something other than delete to release the resource controlled by a scoped_ptr. _____ Rob Stewart robert.stewart@sig.com Software Engineer, Core Software using std::disclaimer; Susquehanna International Group, LLP http://www.sig.com IMPORTANT: The information contained in this email and/or its attachments is confidential. If you are not the intended recipient, please notify the sender immediately by reply and immediately delete this message and all its attachments. Any review, use, reproduction, disclosure or dissemination of this message or any attachment by an unintended recipient is strictly prohibited. Neither this message nor any attachment is intended as or should be construed as an offer, solicitation or recommendation to buy or sell any security or other financial instrument. Neither the sender, his or her employer nor any of their respective affiliates makes any warranties as to the completeness or accuracy of any of the information contained herein or that this message or any of its attachments is free of viruses.

Dmitry Goncharov wrote:
Andrey Semashev wrote:
Noncopyable nature doesn't mean its type is insignificant. This change would break the interface as the following code won't work anymore:
template< template< typename > class Ptr > void make_a(Ptr< A >& p);
scoped_ptr< A > p; make_a(p);
This may not be the most frequent use pattern but nonetheless it's valid.
I'd opt for this change if it wouldn't affect the interface in the breaking way. Otherwise, I like the way it is.
The difficulty that your example demonstrates is caused by the nature of template template parameters. scoped_ptr should not be blamed for that.
I didn't blame it. And I don't consider my example difficult, anyway. It provides a nice abstraction from the actual pointer type while not introducing excessive genericity. I can't remember the exact code but I surely used such technique somewhere in my code. Not sure it involved scoped_ptr, though...

I didn't blame it. And I don't consider my example difficult, anyway. It provides a nice abstraction from the actual pointer type while not introducing excessive genericity. I can't remember the exact code but I surely used such technique somewhere in my code. Not sure it involved scoped_ptr, though...
What's the status about this? If we don't want to "break" the interface, we can always provide an option to enable this feature, something like (same for scoped_array): template<class T #ifdef BOOST_SCOPED_PTR_ENABLE_DELETER , class D = boost::checked_deleter<T> > #endif class scoped_ptr { // ... public: ~scoped_ptr() { // ... #ifdef BOOST_SCOPED_PTR_ENABLE_DELETER deleter()(ptr); #else boost::checked_delete(ptr); #endif } }; BOOST_SCOPED_PTR_ENABLE_DELETER is not defined by default. There are lots of samples like this in other boost libraries, is it reasonable?

Opened a ticket on asio https://svn.boost.org/trac/boost/ticket/3476 about an attempt to copy-construct an iterator from a singular iterator. Please, have a look BR, Dmitry

On Tue, Sep 22, 2009 at 3:18 AM, Berserker <berserker_r@hotmail.com> wrote:
What's the status about this? If we don't want to "break" the interface, we can always provide an option to enable this feature, something like (same for scoped_array):
template<class T #ifdef BOOST_SCOPED_PTR_ENABLE_DELETER , class D = boost::checked_deleter<T> > #endif class scoped_ptr { // ...
public: ~scoped_ptr() { // ...
#ifdef BOOST_SCOPED_PTR_ENABLE_DELETER deleter()(ptr); #else boost::checked_delete(ptr); #endif } };
BOOST_SCOPED_PTR_ENABLE_DELETER is not defined by default. There are lots of samples like this in other boost libraries, is it reasonable?
No it is not reasonable (IMO). If I write new Boost library A using the deleter, and you write library B not using it, can anyone use *both* of our no doubt great and valuable libraries together in their code? Tony

Gottlob Frege wrote:
On Tue, Sep 22, 2009 at 3:18 AM, Berserker <berserker_r@hotmail.com> wrote:
What's the status about this? If we don't want to "break" the interface, we can always provide an option to enable this feature, something like (same for scoped_array):
template<class T #ifdef BOOST_SCOPED_PTR_ENABLE_DELETER , class D = boost::checked_deleter<T> > #endif class scoped_ptr { // ...
public: ~scoped_ptr() { // ...
#ifdef BOOST_SCOPED_PTR_ENABLE_DELETER deleter()(ptr); #else boost::checked_delete(ptr); #endif } };
BOOST_SCOPED_PTR_ENABLE_DELETER is not defined by default. There are lots of samples like this in other boost libraries, is it reasonable?
No it is not reasonable (IMO). If I write new Boost library A using the deleter, and you write library B not using it, can anyone use *both* of our no doubt great and valuable libraries together in their code?
+1 IMO, the best solution is to develop a new pointer. std::unique_ptr implementation would be a nice addition to boost.

IMO, the best solution is to develop a new pointer. std::unique_ptr implementation would be a nice addition to boost.
Dunno if it was said before, but maybe we could move http://www.boost.org/doc/libs/1_40_0/doc/html/boost/interprocess/unique_ptr.... the smart pointers library. Philippe
participants (9)
-
Andrey Semashev
-
Berserker
-
Dmitry Goncharov
-
Felipe Magno de Almeida
-
Gottlob Frege
-
Howard Hinnant
-
Philippe Vaucher
-
Stefan Strasser
-
Stewart, Robert