Re: setter for deleter in boost::shared_ptr (and alike)
* Daniel Kr?gler:
On 26 Sep., 03:38, "Alf P. Steinbach" <al...@start.no> wrote:
* Daniel Kr|gler:
I do see the point that there cannot exist Foo
objects of automatic storage duration,
Yes, and that's one main point. Given N classes with on average M
constructors each, would you rather write N friend declarations or N*M
factory functions? When it can be easy and simple, why make it
difficult and error-prone?
Why not taking advantage of the up-coming function parameter
packs (as of N2369, section [temp.variadic])? Note that there is a
current extension proposal underway, which IMO would solve the
combinatoric explosion problem of mentioned factory functions:
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2007/n2351.htm#creation
One reason for not using parameter packs is that they don't exist.
At least not with the compilers I use.
I think that's a strong reason.
Probably I have a biased p.o.v. on this issue, but according to my
remembrance of smart-ptr usages which I have seen and worked
with, I count much more which needed to use a specific resource
release functor (because a simple delete was not the proper one)
compared to those which needed to have a non-public destructor.
As already stated, those are two independent issues. Solving one
doesn't preclude solving the other. They're not in opposition, except
to the degree that some solution that tries to address both concerns
unnecessarily removes freedom of design.
No, in that case you're duplicating the specification of deleter, in
client code -- unless you go the N*M factory function route --
Umpteen times, where Umpteen is the number of instantiations of class
Foo, could be hundreds or even thousands. Duplication is evil, and
handing responsibility to client code for invoking the right Magical
Incantation every time is evil. Duplication of magical incantation
Umpteen times is Very Evil(TM): unnecessary work, and error-prone.
I think that the make_shared facility solves most of the factory
explosion issues.
In some years' time, perhaps. Something that doesn't exist today has no
value whatsoever today.
You can even
ensure, that no-one else but shared_ptr<Foo> can
invoke deleter::operator()(const Foo*) by introducing
an additional friend-"connection" between the deleter
and shared_ptr.
Not sure what you mean, technically. But anyway, restriction to a
specific smart pointer for a given class, is a separate issue. One way
is to obfuscate the use of new-expressions so that a simple "new Foo"
will not compile.
I wanted to explain a route to realize that neither delete Foo(42) nor
your std::destroy(new Foo(42)) would be feasible expressions,
because no-one else but the (hosting) shared_ptr should be allowed
to clean-up the protected resource. And to realize that I proposed
the following friend-connection:
Foo -> Delete functor (with *private* operator()(const Foo*) const) ->
shared_ptr<Foo> (or some cleverly restricted set of
shared_ptr<U>'s).
Aha.
Obfuscating new-expressions would not solve this issue (it would
only solve the "restricted creator" problem).
Yes, they're two different issues: restricting creation, and restricting
destruction.
Note that while obfuscated new-expressions don't solve the restriced
destruction problem, the friendship thing does not solve the restricted
creation problem.
But I think the restrictions you propose are too harsh, because they
would have to be in place for the general destroy functor. They would
restrict also where one doesn't want a restriction.
I think what I suggested at the start, a restriction only on which
function or functions or functors can destroy (namely a std::destroy),
where you don't care who or what calls that function, is far more practical.
For shared_ptr is not a universal solution, some people actually also
use other smart pointers!
What does get_deleter solve? It gives access to the destruction
function for the contained pointer, presumably in order to transfer the
raw pointer and destruction function somewhere else. And to the degree
that that's an in-practice need, it is because shared_ptr offers no way
to specify the destruction other than by providing a destruction
function dynamically, as part of the object. With std::destroy the
custom destruction function can be statically associated with the class.
I think that this artifically restricts many valid release use-cases
of
shared_ptr (or I don't understand what you want to explain here).
No, a std::destroy doesn't restrict anything.
It adds a customization point (static, instead of the current dynamic)
and a much needed default.
std::destroy does *only* help you, if you want validly apply delete
on
your object point, but there exists a many-to-one relation of
possible
deleters to a given type (e.g. no-op deleters for objects of static
storage
duration).
Huh? It's the opposite. The current shared_ptr addresses the issue of
many possible different deleters for objects of same type, at the cost
of having to specify the deleters dynamically, at every instantiation.
That's an unnecessary and huge cost. std::destroy eliminates that cost.
So what about the other possible delete functor types, do
you want to forbid them? Please see also my comparison of shared_ptr
with std::function and it's access to the internal function "object"
in
the other posting (called "target"). But maybe I have misunderstood
your idea?
I'm not sure what you mean.
I can do
class ComplexDeleteFunctorTypeWithThisAndThat { ... };
class Foo { ... };
void destroy( Foo const* p )
{ ComplexDeleteFunctorTypeWithThisAndThat()( p ); }
Trivial, as far as I can see.
I would want it even if there wasn't a connection,
because the lack of a means to specify destruction function statically
is a design level bug.
In my other posting I also approved std::destroy, for consistency
with the uninitialized_* specialized algorithms from
[specialized.algorithms], but std::destroy is limited to represent
*exactly* one destroy mechanism for one type (1-to-1 relation).
Yes.
As I tried to explain above, shared_ptr's deleter allows the very
often occuring use case of different deleter types for one
resource type.
Except the "very often occuring", which I think is overstating things,
yes it does, and at a huge cost that's usually completely unreasonable.
A less unclean choice for that particular function would be to separate
its current to jobs: checking, and retrieving.
As member functions:
template< typename D > bool has_deleter() const; // Check.
template< typename D > D const& deleter() const; // Retrieve.
where deleter() throws or calls terminate if there is no D deleter.
This is also good (and compares well with std::has_facet and
std::use_facet),
but I don't think that it is in principal superior. IMO current
get_deleter realizes
what dynamic_cast<T*> does while your above described (use_)deleter
realizes what dynamic_cast<T&> does - both use-cases are valid and
reasonable.
Personally I prefer the pointer-like signature in this scenario,
because
I'm always annoyed that the route
if (s.has_deleter<D>()) {
const D& d = s.use_deleter<D>();
// use d
}
performs the same (possibly expensive) compatibility test *twice*,
while its alternative
if (D* pd = get_deleter<D>(s)) {
//use pd
}
needs only one additional, cheap null test.
The concern is valid, but the solution stinks, IMHO. ;-) Instead, I'd
rather have
template< typename D > D const& deleter_or_ub() const; // Retrieve
in addition to the above.
This can be as fast as you want it to, and it does not introduce
additional UB compared to the pointer return type.
What it does is to separate concerns and make the choices explicit.
And that's nearly always a Good Thing.
An abstract deleter functor where you don't need to know the exact type
would incur about the same overhead as a shared_ptr: it would have to do
essentially the same as a shared_ptr internally. So I'm less sure about
how good or bad it would be to abstract up to that level.
Another solution would be that get_deleter returns a std::function
instance
of the shared_ptr deleter, but that could end in series of wrapped
wrapper
functions...
I'm not sure that a future std::function can represent an arbitrary functor.
Anyway, if it can, then it incurs about the same overhead as a shared_ptr.
And then it would not be "another solution", but just what I discussed.
And if it can't, then it is nothing but an unnecessary wrapper where the
client code still needs to know the exact type.
So I don't see what std::function could be "another solution" to.
[snip]
Cheers, & hth.,
- Alf
--
A: Because it messes up the order in which people normally read text.
Q: Why is it such a bad thing?
A: Top-posting.
Q: What is the most annoying thing on usenet and in e-mail?
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]