Boost logo

Boost :

From: Yitzhak Sapir (yitzhaks_at_[hidden])
Date: 2002-10-02 13:50:42


> -----Original Message-----
> From: scleary_at_[hidden] [mailto:scleary_at_[hidden]]
> Sent: Wednesday, October 02, 2002 5:39 PM
> To: boost_at_[hidden]
> Subject: RE: [boost] [smart_ptr] const-correctness as
> function argument
>
> >
> > How can the compiler optimize a scoped_ptr?
>
> If it's const, the compiler can assume its value doesn't
> change. There are a
> few exceptions the compiler has to watch out for (e.g., const
> casting), but
> this is the general idea behind value propogation and loop strength
> reduction.

Are those just a few exceptions? I think I read in D&E a discussion that const is almost useless (but I can't locate D&E now, so I can't look it up). Searching for material on this now, I find a reference to an article by Andrew Koenig (How much does const promise? - C++ Report/1997) that apparently seems to make the same point but I don't know because I haven't read the article. Another page I found gave an example that the compiler must make sure that "this" is not saved in any of the constructors as this would allow the object to access itself non-constly. This means the compiler must know the content of the constructor with which your optimization candidate was initialized, and the content of the constructors used in the initializer list of that constructor for each the object's members, etc etc recursively, before considering optimizations. Maybe I'm just talking nonsense, but it was just my (perhaps-mistaken) understanding that compilers cannot really do optimizations based on const, without a lot
 of work that is not worth it just for this optimization capability. Anyway, this is all I can say on the subject :)

> > So does const auto_ptr<T> x(..);
> > It turns out that const auto_ptr and const scoped_ptr both
> mean almost the
> same thing.
>
> Agreed.
>
> So why redefine const scoped_ptr to mean something
> *completely* different?
> We would only be asking for confusion...

No. It would be duplication. Having two things in a library (or set of libraries, and boost does try to unite itself with the standard libraries) that do the same thing is duplication, not confusion reduction. In fact, there's also an argument of confusion for having two things that do the exact same thing and yet are not interchangeable. If we now have:

f(const scoped_ptr<int>& a)

who is to prevent a user (in another module) from wanting to do:

const std::auto_ptr<int> p(new int(3));
f(p);

or the other way around? In fact, this type of duplication of concepts is one reason for having a library -- to avoid duplication.

On the other hand, there is definitely a use, when used as a scoped holder inside a class, for a member pointer class that obeys const rules (such as impl pointers). Such an adaptor is the type of thing people might end up writing over and over (since it's there for a design concept that people use over and over). I don't think it should be disallowed from being in a library, given that a library is mainly, in my view, to prevent people from having to reinvent the wheel and implementing the "wheel" in the best way.

We have two objects that behave the same. const auto_ptr, and const scoped_ptr. Why not specialize one to handle one case people use over and over, and the other to handle a different one? A person using a library is supposed to be able to know (from documentation) what object to use for a specific case, and verify when he uses the class that it is what he wants to use from the documentation. A person does not write something because he thinks it does what he wants, without checking the documentation. For example, using find vs. search requires understanding the differences between the two. So I don't think requiring a user to understand the differences between auto_ptr and scoped_ptr in the case of such const behavior would be confusing.

Now, if you accept these arguments, we end up with a scoped_const_ptr adaptor/class. Why can't we do away with the functionality of scoped_ptr in the case of const, and do away with the otherwise unnecessary scoped_const_ptr, by having such const/non const operator->/*()? If people want the "const scoped_ptr" functionality you (and Ed Brey) mentioned, you can use const auto_ptr. If you want the functionality I'd like somewhere, isn't scoped_ptr a good candidate? If you don't, you are probably just forcing people to have to reinvent this wheel. (Having a policy smart pointer, and saying that users can now just typedef, is still sort of reinventing the wheel, just a "smaller wheel". They still have to write the typedef over and over).

> > For a moment I thought scoped_ptr takes a bit more space
> > because you can specify a deleter function. I guess it
> > doesn't let you do that, and takes up the same space. Why
> > this asymmetry between shared_ptr and scoped_ptr? Also, why
> > aren't there scoped_dynamic_cast and friends?
>
> I don't know about the deleter function question. But the
> scoped_dynamic_cast one is easy: there is no assignment of
> scoped pointers.

Hm, I can see why implementation wise, but conceptually, I just want a different view of this object. I'd have liked something equivalent to:
B* p;
dynamic_cast<D*>(p)->....

But I suppose it's too much work as compared to me just doing:
dynamic_cast<D*>(p.get())->...

There's still the deleter assymetry issue, though.

> > > I am not in the general habit of passing scoped_ptr's to
> > > functions, but if I
> > > did, I would expect:
> > > void f(const scoped_ptr<T> &);
> > > to mean:
> > > f() cannot change the value of the pointer object
> > > not:
> > > f() cannot modify the object being pointed to
> >
> > Is it desirable to allow or support the passing of
> > scoped_ptr's to functions? Both f(scoped_p.get()) and
> > f(scoped_p) must assume that scoped_p does not lose
> > ownership.
>

We are dealing with the case that scoped ptr is const. Not any other case, so:

> f(T *); // no ownership concept at all
> f(scoped_ptr<T> &); // ownership may be transferred

This is only what we know:

> f(const scoped_ptr<T> &); // no ownership transfer

And this is what I said.

> Thus, f(scoped_p) might or might not lose ownership; this
> depends on the
> interface of f().

Like you say later, this is a reason to use auto_ptr.

> Scoped pointers may not actually be passed to or returned
> from functions,
> while auto_ptrs can be. It is possible to add the same
> workarounds used for
> auto_ptr to scoped_ptr, but if you're going to pass or return
> a scoped_ptr
> to or from a function, then you really need a smart pointer
> that is capable
> of transferring ownership -- that is, auto_ptr.
>
> The functions above (and the ones starting this thread) pass
> *references* to
> scoped pointers.

The one in the start of the thread passed a reference to a shared_ptr, not a scoped_ptr. I had taken the point that scoped_ptr would be a candidate for such a thing. I think even passing a scoped_ptr nonconst& is bad. If you need the ownership transfer capability, use auto_ptr. And passing a const& is not that much different from passing p.get(). It might even be possible to say that in the case of f(T*), conceptually, the function knows nothing about the ownership of T* and cannot take ownership of it, and the caller only knows the function does not take ownership of p but does not know anything else so it must insure that T* is valid throughout the function call.

I guess my arguments have come down to that in the cases of scoped_ptr that you'd not like a const/nonconst operator->/*, you should use auto_ptr. It's interesting that in both cases (passing to functions, const member/variable) it's for different reasons, but still, it's the same final answer.

As to what I'd tell people if I were asked why there's a scoped_ptr with a double const/nonconst operator-> and not a similar shared_ptr-like object that has double access functions, well, right now, I would answer that such functions suggest the pointer has ownership of the object (this is true, in the case of containers, where you have const-iterators, to the objects the container owns), whereas a shared pointer does not own the object it points to (that's the meaning of shared_ptr). In other words, only when the pointer owns the object it points to, I think there a reason to consider double operator->/* that have const/nonconst return values.


Boost list run by bdawes at acm.org, gregod at cs.rpi.edu, cpdaniel at pacbell.net, john at johnmaddock.co.uk