C++ Logo

std-proposals

Advanced search

Re: Ptr proposal: looking for feedback

From: Jason McKesson <jmckesson_at_[hidden]>
Date: Sat, 18 Jul 2020 11:10:18 -0400
On the technical side of things, there are a few more issues.

The type should not be copyable. Creating non-owning references is
something that can lead to bugs, so it shouldn't be able to happen by
accident. As such, the act of creating a non-owning reference ought to
be *visible* in a piece of code; it ought to be obvious and clear from
inspection that you're removing ownership.

However, copying is the default operation in C++ when you do a lot of
things. Copying looks somewhat invisible most of the time. The
function `func(a_ptr)` is probably making a copy of its argument, but
you can't really tell from just looking at it. You also can't *search*
for it, should you need to start tracking down lifetime issues.

To avoid this, the type ought not have a copy constructor/assignment
operator. Instead, it should have a `nonowning` member function that
returns a prvalue "copy" of the object that doesn't own the pointer.
So for the above example, we have `func(a_ptr.nonowning())`. This
makes it very obvious what is going on, and you can look for places in
your code where you're calling that function.

Also bikeshedding: `ptr` is the wrong name. The type carries ownership
semantics of some kind, but the name doesn't say what they are. The
short name heavily implies to a learning C++ programmer that this
ought to be the "default" smart pointer type, with other smart pointer
types relegated to more specific usage scenarios. And that's
*absolutely* not the case for this type.

Since this type represents "context-dependent ownership", the name
should reflect that. Perhaps `context_dependent_ptr`, or
`dynamic_owning_ptr`. Something that makes it clear that the type may
or may not own something, so you need to be careful with how you use
it.

On Thu, Jul 16, 2020 at 6:32 PM Jefferson Carpenter via Std-Proposals
<std-proposals_at_[hidden]> wrote:
>
> On 7/16/2020 3:49 PM, Jason McKesson via Std-Proposals wrote:> The key
> aspect of your proposal, from what I can tell, is that while
> > Now, we get to the question: is this a thing worth doing?
> >
> > I'm going to have to come down on a hard "no."
> >
> > You make reference to `shared_ptr`, but the whole point of a
> > `shared_ptr` is that *ownership* is shared. It's not merely that you
> > can reference a pointer from multiple locations in your code; it's
> > that any code that has such a pointer maintains ownership of it.
> > Dangling pointers to the object are impossible so long as all of your
> > pointers are shared.
> >
> > Your `ptr` type is basically made for dangling pointers.
>
> I disagree - it's no more dangerous than any other movable type. You
> can inspect any function body and see if the usage is correct - if it's
> correct locally in each place, then it's correct globally.
>
> Also if some code that you call into would cause dangling pointers, you
> can simply not std::move into it; ownership will never be transferred to
> it, and you've patched over the problem.

That's pretty much the best argument I've seen for why this type
should *not* be in the standard. If there's some body of code with the
kind of incoherent ownership semantics that lead to dangling pointers,
then that code has bugs in it. Wallpapering over bugs is not how you
fix them; *fixing them* is how you fix them. Wallpaper may be
expedient for the immediate future, but it has costs down the line.

The C++ standard library should not promote bad programming practices,
and it should avoid having types that actively encourage them.

The fundamental problem with what you call "context-dependent
ownership" is that it makes code hard to reason about. A particular
piece of code can never *in isolation* be deemed "reasonably memory
safe"; it can only be "memory safe" within a specific context. A
context that cannot be verified statically; it can only be verified
dynamically, at runtime.

If a function takes a `unique_ptr` by value and it stores it in an
object, there's no need to think about it further. There's no need to
write tests for what happens if you use the object after the pointer
is deleted because you *cannot do that*.

Any similar function which takes a `ptr` by value now has to have a
bunch of tests written for it to see what happens if you play around
with when the object it points to gets destroyed. That represents
higher maintenance costs.

Encoding ownership semantics statically rather than through dynamic
values or implicit expectations is why we invented smart pointers in
the first place. They make it either straight-up impossible or at
least really difficult to make certain categories of mistakes.

Static ownership semantics are better for maintainability of code, for
legibility, for solving problems before they happen, etc. By adding a
pointer that has "context-dependent ownership" semantics, you
basically encourage people to throw all of those advantages away.
Having such a type creates a path of least resistance, a way to avoid
the annoyances of tracking ownership statically. And that will quickly
become a noob-trap, a type that newbies use because it's quick and
easy without understanding how to use it correctly.

And then they write a bunch of bad code that other people have to deal
with down the line.

To the extent that this type is useful, it is a scalpel: a tool you
use for very specific cases. Not everyone needs this tool. That
combined with the inherent dangers of overuse of the tool, means that
the standard library shouldn't provide it. Plus, it's not that hard to
write yourself, so if you need one, you can make one yourself.

Received on 2020-07-18 10:13:46