Re: setter for deleter in boost::shared_ptr (and alike)
Alf P. Steinbach wrote:
* Kai-Uwe Bux:
Alf P. Steinbach wrote:
* James Dennett:
You *may* be able to assign to it,
There is no "may" about it.
Unless you're talking again about an inaccessible assignment operator,
which has nothing to do with what shared_ptr enforces or not.
Of course it has to do with that. The design of shared_ptr<> opting for
get_deleter and leaving out
template < typename D >
set_deleter( D d )
is enforcing that the type of the deleter cannot be changed. That client
code _can_ disable assignment is a direct consequence of the guarantees
made by shared_ptr.
I'm sorry, that's incorrect.
It is a little imprecise. The client can prevent a change of the deleters
behavior because of this.
As long as shared_ptr does not require assignable deleters, a deleter
can be non-assignable.
True but irrelevant. The point is that there is a definite difference in
guarantees made by
shared_ptr {
template < typename D >
D * get_deleter();
};
and
shared_ptr {
template < typename D >
D * get_deleter();
template < typename D >
void set_deleter( D d );
};
The later would allow replacing the deleter object no matter what. Making
the deleter non-assignable would buy you nothing. That making the deleter
non-assignable isn't pointless is because shared_ptr guarantees the identity
of the deleter object (although not the invariance of its value).
No matter what guarantees shared_ptr provide or not, that's still the
case: assignability of the deleter itself has nothing to do with what
shared_ptr enforces or not, but the ability to assign if the deleter is
assignable, the ability to change the effective deleter, /is/ a direct
consequence of lack of enforcement.
It's a consequence of design. I do not see a lack of enforcement because I
do not agree that changing the value of the deleter is a bad idea per se.
We should not loose sight of the main contention that the return type of
get_deleter() being non-const is a design flaw. That has been claimed by
you
Right.
(but not demonstrated).
I'm sorry, that incorrect.
It is. You have demonstrated that there are funny things that can happen
when you change the value of a deleter of type pointer-to-function. You
have not demonstrated that get_deleter() returning a non-const pointer is
at fault:
Consider the following patches to TR1:
a) Changing the return type of get_deleter():
template < typename D >
D const * get_deleter()
b) Changing the conceptual requirement on the deleter:
The deleter shall be copy constructible _and of class type_.
I think both patches would take care of the examples you provided.
Therefore, you have not demonstrated that the fault lies with the return
type.
All three or four articles before my first appeared in this thread,
explained how having a getter but not a setter is extremely desirable
and is tied directly to the fundamental purpose of shared_ptr.
They argued why a
template < typename D >
set_deleter( D d )
is bad. That is an entirely different matter.
It really needs no further demonstration, but just to help, consider why
you can't change the contained raw pointer similarly.
Others have issues with that.
May be, but they haven't voiced their technical reasoning or even that
they have issues, and I don't know who they could be; in this thread, it
feels like only Humpty Dumpty word redefinitions have been argued.
The discussion of
whether "replace" means change value or means change object identity is
an irrelevant side issue
Right.
[snip]
Right. This should be about const-correctness. It has been demonstrated
in code that the non-const return type does not prevent (or make
difficult) safe usage of shared_ptr, i.e., at the point where you say
shared_ptr ptr ( new X ... , my_deleter );
the type of my_deleter is _your_ choice. If it is class type. you _know_
which operator() will be executed upon deletion. No subsequent assignmen
can void the guarantess you put into this line.
So what exactly is the flaw?
I think you can demonstrate, satisfactorily to yourself, that a
setContainedPointer(T*) function that can be disabled would not prevent,
or make difficult, safe usage of shared_ptr. Simply refrain from using
it. And for good measure, always disable it, and voil?, safe.
Please take a few moments to actually demonstrate that, to your own
satisfaction, before reading on.
Not responsive to the question. You have to distinguish two levels of client
code: (a) the code that initializes the shared_ptr and (b) the code that
uses the shared_ptr later. Invariance of the deleter object implies that
code of (a) type can make guarantees that cannot be subverted by subsequent
actions in code of type (b). That would not be true if there is a
set_deleter template. Thus your setContainedPointer(T*) analogy is missing
the point.
[snip]
I've demonstrated by actual code that you can change the delete action,
what actually /happens/ when a shared_ptr invokes its deleter, at will
(if there is an accessible operator of course).
That is possible if the type of the deleter is a function pointer. For
class types, the code of operator()(T*) will be executed no matter what
you try.
The point isn't that shared_ptr can't be used in safe ways.
The point is that with the current design you can make guarantees in (a)
type code without requiring (b) type code from refraining voluntarily to to
funny stuff.
Anything
can be used in safe ways, even goto. It's the opposite view that is of
interest when we talk about what the design enforces, namely the fact
that it needlessly and rather easily can be used in unsafe ways.
Needlessly: other than those unsafe ways necessitated by its very
nature, e.g. circular dependencies.
Ok, let's talk concrete design changes. I propose the wording that the
deleter shall be of class type as an additional requirement. I would not
change the return type of get_deleter() to non-const (although, maybe there
should be a const and a non-const version of get_deleter). With that little
change, which preserves the current flexibility (see below), your examples
would no longer apply. The conceptual requirement would make it clear that
at the point where you initialize a shared_ptr you have to take
responsibility of what can be done with the deleter object.
I agree that the ideal would be that you couldn't.
I disagree that this would be idea. Somehow, my previous post did not get
through, so I reproduce the use case here:
template < typename T >
class MyDeleter {
typedef std::tr1::function< void(T*) > command;
std::list< command > bye_bye;
public:
template < typename Command >
void push_command ( Command c ) {
bye_bye.push_back( c );
}
void operator() ( T * t_ptr ) const {
for ( std::list< command >::const_iterator iter = bye_bye.begin();
iter != bye_bye.end(); ++ iter ) {
(*iter)(t_ptr);
}
free (t_ptr);
}
};
Note that this deleter has an accessible assignment operator. Indeed, you
can use that to ditch all registered actions, but you cannot use it to
change the deletion from free() to, says, delete[].
Uhm, good point, I didn't think of that: the current shared_ptr design
allows you to dynamically add or remove destruction side-effects, per
contained pointer, without using an extra indirection or wrapper.
On the other hand, I've never encountered that need.
And if this was seemingly really needed, I don't think the extra
indirection (of letting the shared_ptr contain a pointer to a C++ object
doing things in its destructor) or wrapper (of shared_ptr itself), or
perhaps some other solution, would be a prohibitive cost, and it might
even be that a redesign where these dynamically specified destruction
side effects weren't needed, would be to the Good.
Well, that can be a little bit tricky. Putting notification code into the
destructor of the deleted object will notify the clients (who may hold weak
pointers) at a moment when the destruction of the pointee has already
started. They better refrain form doing things like asking the pointee if
there are any last words to be recorded.
Since it is ultimately the shared_ptr that decides when deletion will
happen, the deleter object is the natural point for any clean-up code.
Now, that is clearly a use case. It far outweighs, in my opinion, the
possible dangers of using shared_ptr<> with a stupid deleter.
Bah. :-)
Note that the only stupid deleters so far are function pointers. It is easy
to get rid of them.
[snip]
Best
Kai-Uwe Bux
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]