C++ Logo


Advanced search

Re: Ptr proposal: looking for feedback

From: Jefferson Carpenter <jeffersoncarpenter2_at_[hidden]>
Date: Sat, 18 Jul 2020 19:51:32 +0000
On 7/18/2020 3:10 PM, Jason McKesson via Std-Proposals wrote:
> 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.

Not a bad idea. Actually in my own implementation I seem to have
deleted the copy constructor and assignment operator too, and used
`a_ptr.alias()` in the place of your `a_ptr.nonowning()`.

> 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.

I'll go ahead and use one of those in my next update to this proposal.
Those are reasonable ideas.

> On Thu, Jul 16, 2020 at 6:32 PM Jefferson Carpenter via Std-Proposals
> <std-proposals_at_[hidden]> wrote:
>> 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.

That is correct. Maybe I shouldn't have mentioned it. (the same sort
of wallpapering is possible with std::shared_ptr just so we don't keep
talking about it)

> 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.

"gets destroyed" when? If you're referring to its being destroyed
/during/ the function call, then that is only possible if the function
destroys it or std::moves it into another function call.

You can quickly glance at the function and check whether any operations
on the argument other than the last operation on it are destructive to
it. If so, the implementation is wrong - if not, it's correct wrt that
argument. In other words, use-after-move is the only way that you could
give yourself a dangling pointer. (Let me know if this still isn't
clear, I would love to explain the implementation some more :)

> 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.

That's exactly why this type is so cool, it dynamically manages
ownership in what's logically the most resource-efficient way (by
releasing resources as early as they are not used in program side
effects) in a way that's statically verifiable up to use-after-move
being statically checkable, with the runtime space overhead of a bool.

> 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.

No, I think noobs will continue using new, delete, and T*.

That's a joke.

As I think I mentioned, this class does fall in between unique_ptr and
shared_ptr in some performance properties. However, whether C++ is in
need of another slice that thin, is definitely another thing that would
need to be decided, in addition to agreeing that this type makes sense
from a logical basis.

> Plus, it's not that hard to
> write yourself, so if you need one, you can make one yourself.

Agreed :) It's easy to get right.

Received on 2020-07-18 14:54:52