template <typename T>
class optional_ptr {
public:
optional_ptr() = default;
optional_ptr(T* p) : _p(p), _own(false) {}
optional_ptr(unique_ptr<T> p) : _p(p.release()), _own(true) {}
~optional_ptr() { if(_own) delete p; }
optional_ptr(const optional_ptr<T>&) = delete;
optional_ptr<T>& operator=(const optional_ptr<T>&) = delete;
optional_ptr(optional_ptr<T>&&) noexcept;
optional_ptr<T>& operator=(optional_ptr<T>&&) noexcept;
bool owns() const { return _own; }
T* get() const { return _p; }
T* release() noexcept { auto* p = _p; _own = false; _p = nullptr; return p; }
void reset(T* p);
void reset(unique_ptr<T> p);
//etc...
private:
T* _p = nullptr;
bool _own = false;
};
How have you dealt with the problem of having a viewer which sometimes needs to be an owner? Do you see this as a valid problem that needs addressing or an anti-pattern? For all resources, we have owners who manage the lifetime of the resource and viewers who view the resource. To be correct, all of the viewers have to stop using the pointer before the last owner goes out of scope and destroys the resource.For both unique_ptr and shared_ptr, the set of owners and viewers has to be decided explicitly ahead of time. This can very limiting because I've run into cases where I have types which 99% of the time are viewers (because the resource is often shared) but in some rare contexts need to be an owner because the object is being used standalone with no external entity to manage the ownership.
void do_something(unique_ptr<T> p);void do_something2(T *p);The heavy handed solution to use shared_ptr everywhere. Now everyone is an owner. Using shared_ptr adds runtime overhead to manage the reference count. The biggest problem with shared_ptr is that once you use it in one place, you have to use it everywhere. Everything now must be dynamically allocated individually. You can't allocate 20 objects in an array and view one of them. You can't create objects on the stack or as a data member.
Essentially, optional_ptr is a move only type that works like a variant<T*,unique_ptr<T>>.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Future Proposals" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-proposal...@isocpp.org.
To post to this group, send email to std-pr...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-proposals/.
On Thu, Sep 24, 2015 at 7:35 PM, Matthew Fioravante
<fmatth...@gmail.com> wrote:
> For all resources, we have owners who manage the lifetime of the resource
> and viewers who view the resource. To be correct, all of the viewers have to
> stop using the pointer before the last owner goes out of scope and destroys
> the resource.
If anything, this sounds fragile and dangerous.
But, in case you can easily guarantee that the owner outlives the
viewers, did you consider using unique_ptr with a custom deleter?
On Thursday, September 24, 2015 at 12:35:28 PM UTC-4, Matthew Fioravante wrote:For all resources, we have owners who manage the lifetime of the resource and viewers who view the resource. To be correct, all of the viewers have to stop using the pointer before the last owner goes out of scope and destroys the resource.For both unique_ptr and shared_ptr, the set of owners and viewers has to be decided explicitly ahead of time. This can very limiting because I've run into cases where I have types which 99% of the time are viewers (because the resource is often shared) but in some rare contexts need to be an owner because the object is being used standalone with no external entity to manage the ownership.
... This seems to be a problem with how you're thinking of types and ownerships.
Types do not define ownership. Or at least, not in the way you seem to mean. What defines ownership is code, at compile time.
If I have some function:void do_something(unique_ptr<T> p);
That function is explicitly claiming ownership of the object it is given. The fact that it claims ownership is an integral part of what it means to `do_something`.
If I have this function:void do_something2(T *p);
It is (by convention) not claiming ownership of `p`.
If you want to call `do_something2`, and you have a `unique_ptr<T>`, simply call `get` to fetch the pointer.
If you have a T* and you want to call `do_something`... you can't. You have no right to do so. Because you do not own that object, and you therefore cannot transfer something you don't have.
That's just a good interface.
I'm just having a hard time imagining what a hypothetical `do_something3` function would actually be doing if it does not statically know if it's claiming ownership or not.
If the `do_something3` is a free function, one for which the parameter is not stored beyond its scope, then the concept of ownership is pointless.
Because again, you just pass it a T*. If the caller has a `unique_ptr`, and is finished with that `unique_ptr` immediately after calling `do_something2`, they can simply destroy it right after the call.
So this is only meaningful if you have an object which may or may not own some other object it is given. Can you give a more concrete example of an object that cannot statically know if expects to be given ownership of an object?
The heavy handed solution to use shared_ptr everywhere. Now everyone is an owner. Using shared_ptr adds runtime overhead to manage the reference count. The biggest problem with shared_ptr is that once you use it in one place, you have to use it everywhere. Everything now must be dynamically allocated individually. You can't allocate 20 objects in an array and view one of them. You can't create objects on the stack or as a data member.
You can in fact do all of those things with `shared_ptr`. Simply provide the initial shared_ptr<T> with a deleter that doesn't delete it.
Sure, you still have the reference count overhead which now accomplishes nothing. But other than that, it's a perfectly functional solution.
Essentially, optional_ptr is a move only type that works like a variant<T*,unique_ptr<T>>.
So why not just use a variant<T*, unique_ptr<T>>? Just create a simple visitor to get a T* out of it.
Because `unique_ptr` is move only, so too will the `variant` containing it. It does everything you want, just not quite with the interface you'd prefer.
Not only that, it gives you the flexibility to ask the question: do I actually have ownership of this object? Your proposed interface doesn't provide taht facility.
Which means that your class can only talk to code that either itself doesn't know or that takes a T*.
It can't call code that takes a unique_ptr<T>.
Also, remember that `unique_ptr` has options as well. It could take an array, and it has a deleter type. With the `variant` case, users can specify options as needed. In your proposed syntax, they can only use the conceptual equivalent of unique_ptr<T>.
We have an optional ownership pattern at Google, involving storing an enum next to the pointer. It's fairly bad, but it gets the job done.
I suspect an optional-ownership class isn't the most important thing the committee could be spending time on, but it's not crazy.Using unique_ptr deleters for this is clever, which is as much criticism as it is praise. Having a deleter that doesn't delete makes uses of unique_ptr harder to read, and to some extent is an indication that move-only types are too hard to write.
On Thursday, September 24, 2015 at 1:53:47 PM UTC-4, Jeffrey Yasskin wrote:We have an optional ownership pattern at Google, involving storing an enum next to the pointer. It's fairly bad, but it gets the job done.Hi Jeffrey, could you expand on some specific use cases? Why did you feel the need to invent optional ownership? Why were the built in unique and shared ownership patterns not adequate?
I suspect an optional-ownership class isn't the most important thing the committee could be spending time on, but it's not crazy.Using unique_ptr deleters for this is clever, which is as much criticism as it is praise. Having a deleter that doesn't delete makes uses of unique_ptr harder to read, and to some extent is an indication that move-only types are too hard to write.optional_ptr has to be a move only type because it might own even though copying is perfectly valid if it does not own. It feels a bit odd but in practice it hasn't been a problem for me. If you want to copy that means what you really want is to copy the pointer and have a view, in which case you can call .get() when can then be passed on to T* or another optional_ptr which will not be an owner.
On Thu, Sep 24, 2015 at 7:35 PM, Matthew Fioravante
<fmatth...@gmail.com> wrote:
> For all resources, we have owners who manage the lifetime of the resource
> and viewers who view the resource. To be correct, all of the viewers have to
> stop using the pointer before the last owner goes out of scope and destroys
> the resource.
If anything, this sounds fragile and dangerous.
On Thu, Sep 24, 2015 at 11:32 AM, Matthew Fioravante <fmatth...@gmail.com> wrote:
On Thursday, September 24, 2015 at 1:53:47 PM UTC-4, Jeffrey Yasskin wrote:We have an optional ownership pattern at Google, involving storing an enum next to the pointer. It's fairly bad, but it gets the job done.Hi Jeffrey, could you expand on some specific use cases? Why did you feel the need to invent optional ownership? Why were the built in unique and shared ownership patterns not adequate?
Many uses are in wrappers around other data, for example, a gunzip view on a string or a filtered view of a table, where the library fundamentally only needs a view on the data, but in many cases the user doesn't want to deal with stashing ownership of the data somewhere else. Uses are about evenly split between ones that want the library to take ownership and ones that don't.
On Thursday, September 24, 2015 at 12:54:55 PM UTC-4, Andrey Semashev wrote:On Thu, Sep 24, 2015 at 7:35 PM, Matthew Fioravante
<fmatth...@gmail.com> wrote:
> For all resources, we have owners who manage the lifetime of the resource
> and viewers who view the resource. To be correct, all of the viewers have to
> stop using the pointer before the last owner goes out of scope and destroys
> the resource.
If anything, this sounds fragile and dangerous.This was my concern as well and what I anticipate to be the biggest argument against this idiom. However I'm not yet entirely convinced that its any less dangerous to the situation where you have a unique_ptr owned by A and viewed by B as a T*. Its up to the programmer to ensure that B's lifetime is within A's lifetime.If someone can identify a situation where optional_ptr is much more dangerous than unique_ptr/T* that would be really helpful.
But, in case you can easily guarantee that the owner outlives the
viewers, did you consider using unique_ptr with a custom deleter?I'm not sure how a custom deleter would help. If the deleter does nothing, then you just have T*, if the deleter conditionally deletes the object based on a boolean state, you have my optional_ptr. A reusable common pattern should have a name, not be a backwards hack on a unique_ptr deleter.
On Thursday, September 24, 2015 at 1:36:37 PM UTC-4, Nicol Bolas wrote:
On Thursday, September 24, 2015 at 12:35:28 PM UTC-4, Matthew Fioravante wrote:For all resources, we have owners who manage the lifetime of the resource and viewers who view the resource. To be correct, all of the viewers have to stop using the pointer before the last owner goes out of scope and destroys the resource.For both unique_ptr and shared_ptr, the set of owners and viewers has to be decided explicitly ahead of time. This can very limiting because I've run into cases where I have types which 99% of the time are viewers (because the resource is often shared) but in some rare contexts need to be an owner because the object is being used standalone with no external entity to manage the ownership.
... This seems to be a problem with how you're thinking of types and ownerships.
Types do not define ownership. Or at least, not in the way you seem to mean. What defines ownership is code, at compile time.Not sure I agree, unless I'm misunderstanding you somehow. If a type stores an object by unique_ptr or shared_ptr, that type defines ownership of the resource. If the type stores by T*, he defines non-ownership.
Because again, you just pass it a T*. If the caller has a `unique_ptr`, and is finished with that `unique_ptr` immediately after calling `do_something2`, they can simply destroy it right after the call.
So this is only meaningful if you have an object which may or may not own some other object it is given. Can you give a more concrete example of an object that cannot statically know if expects to be given ownership of an object?Sure, one real example I have is a pipeline. lets say we have pipeline that manages 3 data streams A, B, and C with a configurable number of stages. Each stage can apply some transformation to A, B, or C or just pass it along. When a stage needs to modify a stream, he gets a view of the previous stream, allocates and takes ownership of a new one, and then applies his transformations to convert A -> A`. If he does not need to modify a stream, he just passes along a view of it.
void stage(unique_ptr<A> streamA, unique_ptr<B> streamB, unique_ptr<C> streamC);unique_ptr<A> oldValue = std::move(streamA);
streamA = make_unique(...);This pattern can be accomplished using shared_ptr but again its unecessary. It can also be accomplished with unique_ptr but then each stage has to make a copy of each stream and copy the data even if he does not modify it.
So I remain confused as to when you would need this functionality.
| 0 | 1 | 2 | 3 |
A0 -> | A0 | A0 | A1 | A1 | ->
B0 -> | B0 | B0 | B0 | B1 | ->
C0 -> | C1 | C2 | C3 | C4 | ->
using s = stream;
struct stage {
stage(s* ain, s* bin, s* cin, optional_ptr<s> aout, optional_ptr<s> bout, optional_ptr<s> cout)
: inputs{ain, bin, cin}, outputs{std::move(aout), std::move(bout), std::move(cout)} {}
s* inputs[3];
optional_ptr<s> outputs[3];
virtual void process_next() = 0;
};
struct stage0 : public stage {
stage0(s* a, s* b, s* c)
: stage(a, b, c, a, b, std::make_unique<stream>()) {}
void process_next() {
outputs[2].push_back(transform(inputs[2].back()));
}
};
class stage {
public:
stage(T* input) : _input(input), _output(std::make_unique<T>()) {}
T* input() { return _input; }
T* output() { return _output.get(); }
virtual void process_next() = 0;
private:
T* _input;
unique_ptr<T> _output;
};class noop_stage : public stage {
virtual void process_next() {
_output()->push_back(input()->back());
}
};class stage {
protected:
stage(T* input, optional_ptr<T> output) : _input(input), _output(std::move(output)) {}
public:
T* input() { return _input; }
T* output() { return _output.get(); }
virtual void process_next() = 0;
private:
T* _input;
optional_ptr<T> _output;
};
class noop_stage : public stage {
public:
noop_stage(T* input) : stage(input, input) {}
virtual void process_next() { /* do nothing! */ }
};
class real_stage : public stage {
public:
real_stage(T* input) : stage(input, std::make_unique<T>()) {}
virtual void process_next() { output()->push_back(transform(input()->back())); }
};class pipeline {
public:
pipeline(T* input) : _input(input);
T* input() { return _input; }
T* output() { return _stages.empty() ? input() : _stages.back().output(); }
template <typename S, typename... Args>
void append_stage<S>(Args&&... args) {
_stages.push_back(std::make_unique<S>(output(), std::forward<Args>(args)...);
}
void process_next() {
for(auto& s: _stages) {
s->process_next();
}
}
private:
T* _input;
std::vector<std::unique_ptr<stage>> _stages;
};For all resources, we have owners who manage the lifetime of the resource and viewers who view the resource. To be correct, all of the viewers have to stop using the pointer before the last owner goes out of scope and destroys the resource.For both unique_ptr and shared_ptr, the set of owners and viewers has to be decided explicitly ahead of time. This can very limiting because I've run into cases where I have types which 99% of the time are viewers (because the resource is often shared) but in some rare contexts need to be an owner because the object is being used standalone with no external entity to manage the ownership.The heavy handed solution to use shared_ptr everywhere. Now everyone is an owner. Using shared_ptr adds runtime overhead to manage the reference count. The biggest problem with shared_ptr is that once you use it in one place, you have to use it everywhere. Everything now must be dynamically allocated individually. You can't allocate 20 objects in an array and view one of them. You can't create objects on the stack or as a data member. Forcing individual dynamic allocation is a performance killer. We use C++ because we need to be fast and a big part of being fast beings avoiding allocations or failing that, batching them into fewer allocations of large contiguous cache friendly chunks.The solution I'm toying with now is something called optional_ptr. Maybe its not such a great name because it could be confused with std::optional but lets leave bikeshedding aside for now.
--
How about generalizing this idea to a erased_ptr<T> which can be constructed from a unique_ptr&&, a shared_ptr, a weak_ptr or a plain ptr + bool.
void f(optional_ptr<T> ptr);
T* p = new T();
f({p, true});
f(std::unique_ptr<T>(p));
On Wednesday, September 30, 2015 at 10:50:12 AM UTC-4, Bengt Gustafsson wrote:How about generalizing this idea to a erased_ptr<T> which can be constructed from a unique_ptr&&, a shared_ptr, a weak_ptr or a plain ptr + bool.While the erased_ptr construct seems like it might be more generic and therefore better, I'm reluctant to go down this path because I don't have any use case which would actually need to use an erased_ptr thats sometimes unique, sometimes a view, and sometimes shared. Do you have a use case for this?Also I'm not sure I like the (ptr, bool) constructor. The original version I wrote had this but I took it out. There is zero cost to creating a temporary unique_ptr and moving it in, and by doing so you very clearly document that you want the optional_ptr to own the resource, much moreso than ptr, true. Also optional_ptr will be designed to be 100% compatible with unique_ptr with regards to deleters, so if you have an owned resource you want to pass in, make the appropriate unique_ptr with deleter and then move it in.Compare the following callsites:void f(optional_ptr<T> ptr);
T* p = new T();
f({p, true});
f(std::unique_ptr<T>(p));
The second stands out much more than the first and doesn't cost anything after the optimizer inlines it away.Finally my second concern with erased_ptr is performance. optional_ptr can be implemented with zero space overhead for all alignof(T) > 1 types and near zero overhead for retreiving (& with a mask) and deleting the pointer (check conditional first and then delete).I think at this point we have enough legitimate use cases from people to show that this tool is actually a good one when used responsibly and will benefit many users. Lets open the discussion up to bikeshedding the name.Pretty much everyone hates optional_ptr, including myself so does anyone have better ideas?Here are some ideas:semi_unique_ptr (this one is my favorite so far)
optionally_unique_ptr
optional_ptr
dynamic_unique_ptr
maybe_unique_ptr
variant_ptr
unique_or_view_ptr
I like the idea of having unique in the name, because this thing works exactly like a unique_ptr when it has ownership.
--
The heavy handed solution to use shared_ptr everywhere. Now everyone is an owner. Using shared_ptr adds runtime overhead to manage the reference count. The biggest problem with shared_ptr is that once you use it in one place, you have to use it everywhere. Everything now must be dynamically allocated individually. You can't allocate 20 objects in an array and view one of them. You can't create objects on the stack or as a data member. Forcing individual dynamic allocation is a performance killer. We use C++ because we need to be fast and a big part of being fast beings avoiding allocations or failing that, batching them into fewer allocations of large contiguous cache friendly chunks.
Nit on the latter suggestion:shared_ptr(shared_ptr<Y>&&, T*) does not exist, so the above makes an unnecessary copy.This is more like a defect in the Standard than a problem with the above idiom, though.
On 2015–09–30, at 10:50 PM, Bengt Gustafsson <bengt.gu...@beamways.com> wrote:How about generalizing this idea to a erased_ptr<T> which can be constructed from a unique_ptr&&, a shared_ptr, a weak_ptr or a plain ptr + bool.Internally this could be mainly a variant over the different pointer types, but with an operator-> which works as for any smart pointer. The pointer + owned == true constructor could maybe be equivalent to sending in a unique_ptr&&More thinking is definitely needed but intuitively it seemed to increase the usefulness to include also shared ownership in the mix.
On 2015–10–01, at 11:31 AM, David Krauss <pot...@gmail.com> wrote:std::unique_ptr< T, std::function<void(T&)> >