Re: setter for deleter in boost::shared_ptr (and alike)
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
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.
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.
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).
Obfuscating new-expressions would not solve this issue (it would
only solve the "restricted creator" problem).
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).
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). 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 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).
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.
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.
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...
There is one option - which is quite similar to get_deleter
seen from a farther point - and that would be the route which
std::function took. If you consult the section
[func.wrap.func.targ] or the synopsis of class template
function in [func.wrap.func] of N2369 you will notice that
we have there the following three members:
const std::type_info& target_type() const;
template <typename T> T* target();
template <typename T> const T* target() const;
This is effectivly equivalent to get_deleter - although
completely const-correct. The member function target_type()
does not make sense in the general case for shared_ptr,
e.g. if it will invoke delete to free it's resource, so
this asymmetry of shared_ptr compared to std::function
would be the *only* real pro-argument to consider
std::destroy instead of delete as the "natural" deleter.
Huh? I don't get what you're trying to say here.
I wanted to point out, that the function member target()
overloads of std::function are the exact counterparts of a
proper and const-correct get_deleter pair of shared_ptr.
There is only one difference, because shared_ptr can
use delete and in this case the deleter cannot be retrieved
via get_deleter (which D should we ask for?). On the other
hand, std::function can contain no target at all, so the
difference is actually less than I thought in the moment
when I wrote this.
Greetings,
Daniel
--
[ See http://www.gotw.ca/resources/clcm.htm for info about ]
[ comp.lang.c++.moderated. First time posters: Do this! ]