C++ Logo


Advanced search

Re: Ptr proposal: looking for feedback

From: Jefferson Carpenter <jeffersoncarpenter2_at_[hidden]>
Date: Thu, 16 Jul 2020 22:32:05 +0000
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
> multiple `ptr` instances can reference a pointer, only one of these
> objects *owns it*. So really, it's a copyable `unqiue_ptr`, where the
> copies don't own the pointer. That's what your abstract needs to
> communicate.

Thanks for all this feedback. I appreciate it.

> I also find the object to be lacking in useful features (to the extent
> that the `ptr` type is useful). For example, if you have an allocated
> object, you can't put it inside a `ptr` and have the `ptr` adopt
> ownership of it. Apparently, the only way to claim ownership of such
> an object is to use `make_ptr` to construct the pointer. That's not
> very usable.

I see what you mean. The key difference is that std::unique_ptr and
std::shared_ptr /always/ manage ownership (implicitly or by refcount).
On the other hand, ptr ought to be usable in cases where you want to
call functions whose argument type is ptr<T>, but you do not want the
ptr to take ownership. For instance, you might have a T* that you've
allocated separately and want to pass to a function taking a ptr. Or
you might happen to have a stack-allocated value that you want to pass.

Foo f;
func(ptr(&f)); // subject to change

The most natural thing for a ptr to do is to not have ownership.

> At the *very* least, `ptr<T>` should be constructible/assignable from
> an rvalue-reference to a `unique_ptr<T, std::default_deleter>`. This
> represents a transfer of ownership from the `unique_ptr` to the `ptr`.
> But even then, you should have some other constructors and functions
> that allow you to just take a `T*` that will be owned.

Definitely conversions with other smart pointer types were left out.
That ought to be incorporated.

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

> At a type level, I find `ptr` to be incoherent. Allow me to explain by analogy.
> `unique_ptr` as a type represents unique ownership of an object. If a
> function takes a `unique_ptr` by value or rvalue-reference, then you
> know that the function is adopting ownership of some resource. The API
> is clear.
> `shared_ptr` as a type represents shared ownership of an object. If a
> function takes a `shared_ptr` by value or rvalue-reference, then you
> know that the function is adopting ownership of some resource. The API
> is clear.
> What does it mean for a function to take one of these `ptr` types by
> value/rvalue-reference? Is the function adopting ownership of the
> resource or not? That all depends on the value it is given. That's a
> real problem for tracking down bugs surrounding dangling pointers. If
> all of the types are the same, but the *values* in those types mean
> different things, then it's hard to tell from *the type alone* who
> owns a thing and who does not.

`ptr` as a type represents context-dependent ownership of an object. If
a function takes a `ptr` by value or rvalue-reference, then the function
adopts ownership iff the caller had ownership.

> We already have a type that represents a potentially owning pointer in
> C++: a *pointer*. Whether it is owned or not by the code that uses it
> is based on following the chain of value assignments through various
> code.

A pointer doesn't automatically free its resource after its last usage.
This is just another model of tracking when it is safe to free an
object, and freeing it as soon as possible.

> The problem here is your single-type approach. If you want one object
> that represents unique ownership of a resource, that ownership should
> be codified by a type. And if you want that thing to be able to hand
> out non-owning reference objects, those non-owning objects should also
> be codified in their own types. So there should be two types.

Again, this is just a different model of ownership than either one you
mentioned. As I think I mentioned, this is context-dependent ownership
in which the "owner" is theoretically the lattice meet of the usages of
the object in the call graph.

> The thing is... we already have those two types: `unique_ptr<T>` and
> `T*`. If you absolutely must avoid naked pointers, the library
> fundamentals has a proposal for `observer_ptr<T>`, which is explicitly
> a non-owning "smart" pointer. It's not in the standard (yet?), but you
> can write one easily enough.
> So we don't really need such a type. Plus, `unique_ptr` is *much* more
> capable than your type as written.

unique_ptr definitely does different things, but what makes ptr more
capable is that it can free resources as early as possible (even several
calls deep) in functions that are called in several places in function
bodies. For unique_ptr to do that, functions have to be overloaded to
take both T& and std::unique_ptr<T>. (For r to be freed before h exits
for the last time, what would the function signatures be?)

Received on 2020-07-16 17:35:44