Discussion:
std::observer_ptr poorly and misleadingly named
(too old to reply)
Michael Hamilton
2015-10-05 19:19:10 UTC
Permalink
Abstract: observer_ptr as currently proposed does not "observe" its owner
and therefore should not be named observer. The current proposal should
choose a new name to reflect the raw_ptr ownerless nature which doesn't use
existing design pattern nomenclature. There is a use case for wanting to
detect expiration of a unique_ptr, however, and this could be
observer_ptr's purpose.

____

I recently discovered a proposal for observer_ptr
(http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3840.pdf)

It is correctly proposed as the "world's dumbest smart pointer" and I
believe the name itself is bad. I have no issue with the desire for a
self-documenting raw pointer wrapper, but observer is *not* an appropriate
name for such a device.

What does the term observer mean to you in programming? To me it means
something which observes state changes and receives notifications of those
changes.

Wikipedia agrees: https://en.wikipedia.org/wiki/Observer_pattern

"The *observer pattern* is a software design pattern
<https://en.wikipedia.org/wiki/Design_pattern_(computer_science)> in which
an object
<https://en.wikipedia.org/wiki/Object_(computer_science)#Objects_in_object-oriented_programming>,
called the subject, maintains a list of its dependents, called observers,
and notifies them automatically of any state changes, usually by calling
one of their methods
<https://en.wikipedia.org/wiki/Method_(computer_science)>."

This is an established pattern with established terminology, and for better
or worse, the term observer implies notification.

So, what's my beef?

std::unique_ptr is great for simple single-point ownership, it replaces
auto_ptr, is non-copyable, but is move-able.
std::shared_ptr is undeniably useful, but has many caveats in terms of
difficult to debug cyclic ownership scenarios needing to be carefully
managed with weak_ptr instances. The primary issue with shared_ptr is that
the lifetime is complicated and atomic reference counting needs to exist.
std::weak_ptr as mentioned above works hand in hand with shared_ptr. There
is a very handy "expired" method.

Raw pointers are still quite useful, but need to be carefully used in
places where ownership is guaranteed, and typically are the weak_ptr
equivalent to unique_ptr (ie: parent/child relationships with children
pointing to the parent.)

Why not std::raw_ptr, std::unowned_ptr, std::no_owner_ptr, or
std::ownerless_ptr, or std::orphan_ptr?

Why do I care? There is a use case that is not reflected in the above
pointer types which would be much better for using the term observer_ptr.

When one object controls lifespan, but other objects want to observe and
disconnect from the observed object when its lifespan expires! Right now we
are stuck with shared_ptr and weak_ptr and expired, but a cleaner interface
could certainly be created.

Imagine a std::unique_ptr being created, and then constructing something
like:

std::observer_ptr(const std::unique_ptr<T> &a_observed);
std::observer_ptr::expired() //returns true if the observed pointer is dead.

Optionally:
std::observer_ptr(const std::unique_ptr<T> &a_observed, const
std::function<void (T*)> &a_ondestroy); //call a_ondestroy when the
observed pointer is being destroyed.

What kind of use case is this good for? Signals/Slots automatically
deregistering themselves is one obvious example. I would imagine all the
instances of shared_ptr in the code sample below being unique_ptr and
observed_ptr.

Sometimes we want a single point of ownership, but *also* want to detect
when that ownership is broken. It seems like an obvious name for such a
mechanism would be observer_ptr, and it seems like a waste to use it on a
basic raw_ptr naming/self documenting case (especially when that naming is
confusing and misleading as I have already pointed out!)

Any thoughts?

#ifndef __MV_SIGNAL_H__
#define __MV_SIGNAL_H__

#include <memory>
#include <utility>
#include <functional>
#include <vector>
#include <set>
#include <string>
#include <map>
#include "Utility/scopeGuard.hpp"

namespace MV {

template <typename T>
class Reciever {
public:
typedef std::function<T> FunctionType;
typedef std::shared_ptr<Reciever<T>> SharedType;
typedef std::weak_ptr<Reciever<T>> WeakType;

static std::shared_ptr< Reciever<T> > make(std::function<T> a_callback){
return std::shared_ptr< Reciever<T> >(new Reciever<T>(a_callback, ++uniqueId));
}

template <class ...Arg>
void notify(Arg &&... a_parameters){
if(!isBlocked){
callback(std::forward<Arg>(a_parameters)...);
}
}
template <class ...Arg>
void operator()(Arg &&... a_parameters){
if(!isBlocked){
callback(std::forward<Arg>(a_parameters)...);
}
}

template <class ...Arg>
void notify(){
if(!isBlocked){
callback();
}
}
template <class ...Arg>
void operator()(){
if(!isBlocked){
callback();
}
}

void block(){
isBlocked = true;
}
void unblock(){
isBlocked = false;
}
bool blocked() const{
return isBlocked;
}

//For sorting and comparison (removal/avoiding duplicates)
bool operator<(const Reciever<T>& a_rhs){
return id < a_rhs.id;
}
bool operator>(const Reciever<T>& a_rhs){
return id > a_rhs.id;
}
bool operator==(const Reciever<T>& a_rhs){
return id == a_rhs.id;
}
bool operator!=(const Reciever<T>& a_rhs){
return id != a_rhs.id;
}

private:
Reciever(std::function<T> a_callback, long long a_id):
id(a_id),
callback(a_callback),
isBlocked(false){
}
bool isBlocked;
std::function< T > callback;
long long id;
static long long uniqueId;
};

template <typename T>
long long Reciever<T>::uniqueId = 0;

template <typename T>
class Signal {
public:
typedef std::function<T> FunctionType;
typedef Reciever<T> RecieverType;
typedef std::shared_ptr<Reciever<T>> SharedRecieverType;
typedef std::weak_ptr<Reciever<T>> WeakRecieverType;

//No protection against duplicates.
std::shared_ptr<Reciever<T>> connect(std::function<T> a_callback){
if(observerLimit == std::numeric_limits<size_t>::max() || cullDeadObservers() < observerLimit){
auto signal = Reciever<T>::make(a_callback);
observers.insert(signal);
return signal;
} else{
return nullptr;
}
}
//Duplicate Recievers will not be added. If std::function ever becomes comparable this can all be much safer.
bool connect(std::shared_ptr<Reciever<T>> a_value){
if(observerLimit == std::numeric_limits<size_t>::max() || cullDeadObservers() < observerLimit){
observers.insert(a_value);
return true;
}else{
return false;
}
}

void disconnect(std::shared_ptr<Reciever<T>> a_value){
if(a_value){
if(!inCall){
observers.erase(a_value);
} else{
disconnectQueue.push_back(a_value);
}
}
}

void block(std::function<T> a_blockedCallback = nullptr) {
isBlocked = true;
blockedCallback = a_blockedCallback;
calledWhileBlocked = false;
}

bool unblock() {
if (isBlocked) {
isBlocked = false;
return calledWhileBlocked;
}
return false;
}

bool blocked() const {
return isBlocked;
}

template <typename ...Arg>
void operator()(Arg &&... a_parameters){
if (!isBlocked) {
inCall = true;
SCOPE_EXIT{
inCall = false;
for (auto&& i : disconnectQueue) {
observers.erase(i);
}
disconnectQueue.clear();
};

for (auto i = observers.begin(); !observers.empty() && i != observers.end();) {
if (i->expired()) {
observers.erase(i++);
} else {
auto next = i;
++next;
i->lock()->notify(std::forward<Arg>(a_parameters)...);
i = next;
}
}
}

if (isBlocked) {
calledWhileBlocked = true;
if (blockedCallback) {
blockedCallback(std::forward<Arg>(a_parameters)...);
}
}
}

template <typename ...Arg>
void operator()(){
if (!isBlocked) {
inCall = true;
SCOPE_EXIT{
inCall = false;
for (auto&& i : disconnectQueue) {
observers.erase(i);
}
disconnectQueue.clear();
};

for (auto i = observers.begin(); i != observers.end();) {
if (i->expired()) {
observers.erase(i++);
} else {
auto next = i;
++next;
i->lock()->notify();
i = next;
}
}
}

if (isBlocked){
calledWhileBlocked = true;
if (blockedCallback) {
blockedCallback(std::forward<Arg>(a_parameters)...);
}
}
}

void setObserverLimit(size_t a_newLimit){
observerLimit = a_newLimit;
}
void clearObserverLimit(){
observerLimit = std::numeric_limits<size_t>::max();
}
int getObserverLimit(){
return observerLimit;
}

size_t cullDeadObservers(){
for(auto i = observers.begin();!observers.empty() && i != observers.end();) {
if(i->expired()) {
observers.erase(i++);
} else{
++i;
}
}
return observers.size();
}
private:
std::set< std::weak_ptr< Reciever<T> >, std::owner_less<std::weak_ptr<Reciever<T>>> > observers;
size_t observerLimit = std::numeric_limits<size_t>::max();
bool inCall = false;
bool isBlocked = false;
std::function<T> blockedCallback;
std::vector< std::shared_ptr<Reciever<T>> > disconnectQueue;
bool calledWhileBlocked = false;
};

//Can be used as a public SignalRegister member for connecting signals to a private Signal member.
//In this way you won't have to write forwarding connect/disconnect boilerplate for your classes.
template <typename T>
class SignalRegister {
public:
typedef std::function<T> FunctionType;
typedef Reciever<T> RecieverType;
typedef std::shared_ptr<Reciever<T>> SharedRecieverType;
typedef std::weak_ptr<Reciever<T>> WeakRecieverType;

SignalRegister(Signal<T> &a_slot) :
slot(a_slot){
}

SignalRegister(SignalRegister<T> &a_rhs) :
slot(a_rhs.slot) {
}

//no protection against duplicates
std::shared_ptr<Reciever<T>> connect(std::function<T> a_callback){
return slot.connect(a_callback);
}
//duplicate shared_ptr's will not be added
bool connect(std::shared_ptr<Reciever<T>> a_value){
return slot.connect(a_value);
}

void disconnect(std::shared_ptr<Reciever<T>> a_value){
slot.disconnect(a_value);
}

std::shared_ptr<Reciever<T>> connect(const std::string &a_id, std::function<T> a_callback){
return ownedConnections[a_id] = slot.connect(a_callback);
}

void disconnect(const std::string &a_id){
auto connectionToRemove = ownedConnections.find(a_id);
if (connectionToRemove != ownedConnections.end()) {
slot.disconnect(*connectionToRemove);
}
}

bool connected(const std::string &a_id) {
return ownedConnections.find(a_id) != ownedConnections.end();
}
private:
std::map<std::string, SharedRecieverType> ownedConnections;
Signal<T> &slot;
};

}

#endif
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Tony V E
2015-10-05 19:57:02 UTC
Permalink
Post by Michael Hamilton
Abstract: observer_ptr as currently proposed does not "observe" its owner
and therefore should not be named observer. The current proposal should
choose a new name to reflect the raw_ptr ownerless nature which doesn't use
existing design pattern nomenclature. There is a use case for wanting to
detect expiration of a unique_ptr, however, and this could be
observer_ptr's purpose.
____
I recently discovered a proposal for observer_ptr (
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3840.pdf)
It is correctly proposed as the "world's dumbest smart pointer" and I
believe the name itself is bad. I have no issue with the desire for a
self-documenting raw pointer wrapper, but observer is *not* an appropriate
name for such a device.
What does the term observer mean to you in programming? To me it means
something which observes state changes and receives notifications of those
changes.
Wikipedia agrees: https://en.wikipedia.org/wiki/Observer_pattern
"The *observer pattern* is a software design pattern
<https://en.wikipedia.org/wiki/Design_pattern_(computer_science)> in
which an object
<https://en.wikipedia.org/wiki/Object_(computer_science)#Objects_in_object-oriented_programming>,
called the subject, maintains a list of its dependents, called observers,
and notifies them automatically of any state changes, usually by calling
one of their methods
<https://en.wikipedia.org/wiki/Method_(computer_science)>."
Why not std::raw_ptr, std::unowned_ptr, std::no_owner_ptr, or
std::ownerless_ptr, or std::orphan_ptr?
Walter (author of world's dumbest smart pointer) has basically said he
doesn't want to deal with naming anymore. If someone wants a better name,
they should write a proposal.

I agree (as do others on the committee) that observer_ptr is a bad name
(because of Observer Pattern, as you mentioned).

In general, names like "raw_ptr" which might be pronounced as "raw pointer"
and thus ambiguous (in speech) with real raw pointers (ie T *) should be
avoided. (ie "You should just pass a raw pointer here" - did you mean Foo
* or raw_ptr<Foo> ?)

unowned_ptr isn't quite right - the pointer (probably/hopefully) is owned,
just not by you. Same with your other suggested names.
I suggested notmy_ptr. :-) It's someones, just not mine. (This fails my
"speech ambiguity" rule above)

The recent frontrunner, IIRC, is view_ptr (I think this was also my
suggestion, but it could be like calculus with independent creators). This
fits with what "observer" was suppose to mean, and with how "view" is used
in string_view and array_view. But it could also be confusing - if views
are now "things" (array_view, string_view) is a view_ptr a pointer to a
view?


Some of the other suggested names can be found in earlier drafts (N3740).
ie:

aloof_ptr
agnostic_ptr
apolitical_ptr
ascetic_ptr
attending_ptr
austere_ptr
bare_ptr
blameless_ptr
blond_ptr
blonde_ptr
classic_ptr
core_ptr
disinterested_ptr
disowned_ptr
disowning_ptr
dumb_ptr
emancipated_ptr
estranged_ptr
excused_ptr
exempt_ptr
faultless_ptr
free_ptr
freeagent_ptr
guiltless_ptr
handsoff_ptr
ignorant_ptr
impartial_ptr
independent_ptr
innocent_ptr
irresponsible_ptr
just_a_ptr
legacy_ptr
naked_ptr
neutral_ptr
nonown_ptr
nonowning_ptr
notme_ptr
oblivious_ptr
observer_ptr
observing_ptr
open_ptr
ownerless_ptr
pointer
ptr
pure_ptr
quintessential_ptr
severe_ptr
simple_ptr
stark_ptr
straight_ptr
true_ptr
unfettered_ptr
uninvolved_ptr
unmanaged_ptr
unowned_ptr
untainted_ptr
unyoked_ptr
virgin_ptr
visiting_ptr
watch_ptr
watcher_ptr
watching_ptr
witless_ptr
witness_ptr

Not sure any of those are right. (I like borrowed_ptr, but that would
suggest you need to give it back somehow.) How about
for_temporary_use_only_ptr? :-)

Tony
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Tony V E
2015-10-05 19:59:19 UTC
Permalink
cadged_ptr? (http://dictionary.reference.com/browse/cadge)

I'm all for co-opting terms. At least I don't think there will be
ambiguity with other uses.

Tony
Post by Tony V E
Post by Michael Hamilton
Abstract: observer_ptr as currently proposed does not "observe" its owner
and therefore should not be named observer. The current proposal should
choose a new name to reflect the raw_ptr ownerless nature which doesn't use
existing design pattern nomenclature. There is a use case for wanting to
detect expiration of a unique_ptr, however, and this could be
observer_ptr's purpose.
____
I recently discovered a proposal for observer_ptr (
http://www.open-std.org/jtc1/sc22/wg21/docs/papers/2014/n3840.pdf)
It is correctly proposed as the "world's dumbest smart pointer" and I
believe the name itself is bad. I have no issue with the desire for a
self-documenting raw pointer wrapper, but observer is *not* an appropriate
name for such a device.
What does the term observer mean to you in programming? To me it means
something which observes state changes and receives notifications of those
changes.
Wikipedia agrees: https://en.wikipedia.org/wiki/Observer_pattern
"The *observer pattern* is a software design pattern
<https://en.wikipedia.org/wiki/Design_pattern_(computer_science)> in
which an object
<https://en.wikipedia.org/wiki/Object_(computer_science)#Objects_in_object-oriented_programming>,
called the subject, maintains a list of its dependents, called observers,
and notifies them automatically of any state changes, usually by calling
one of their methods
<https://en.wikipedia.org/wiki/Method_(computer_science)>."
Why not std::raw_ptr, std::unowned_ptr, std::no_owner_ptr, or
std::ownerless_ptr, or std::orphan_ptr?
Walter (author of world's dumbest smart pointer) has basically said he
doesn't want to deal with naming anymore. If someone wants a better name,
they should write a proposal.
I agree (as do others on the committee) that observer_ptr is a bad name
(because of Observer Pattern, as you mentioned).
In general, names like "raw_ptr" which might be pronounced as "raw
pointer" and thus ambiguous (in speech) with real raw pointers (ie T *)
should be avoided. (ie "You should just pass a raw pointer here" - did you
mean Foo * or raw_ptr<Foo> ?)
unowned_ptr isn't quite right - the pointer (probably/hopefully) is owned,
just not by you. Same with your other suggested names.
I suggested notmy_ptr. :-) It's someones, just not mine. (This fails my
"speech ambiguity" rule above)
The recent frontrunner, IIRC, is view_ptr (I think this was also my
suggestion, but it could be like calculus with independent creators). This
fits with what "observer" was suppose to mean, and with how "view" is used
in string_view and array_view. But it could also be confusing - if views
are now "things" (array_view, string_view) is a view_ptr a pointer to a
view?
Some of the other suggested names can be found in earlier drafts (N3740).
aloof_ptr
agnostic_ptr
apolitical_ptr
ascetic_ptr
attending_ptr
austere_ptr
bare_ptr
blameless_ptr
blond_ptr
blonde_ptr
classic_ptr
core_ptr
disinterested_ptr
disowned_ptr
disowning_ptr
dumb_ptr
emancipated_ptr
estranged_ptr
excused_ptr
exempt_ptr
faultless_ptr
free_ptr
freeagent_ptr
guiltless_ptr
handsoff_ptr
ignorant_ptr
impartial_ptr
independent_ptr
innocent_ptr
irresponsible_ptr
just_a_ptr
legacy_ptr
naked_ptr
neutral_ptr
nonown_ptr
nonowning_ptr
notme_ptr
oblivious_ptr
observer_ptr
observing_ptr
open_ptr
ownerless_ptr
pointer
ptr
pure_ptr
quintessential_ptr
severe_ptr
simple_ptr
stark_ptr
straight_ptr
true_ptr
unfettered_ptr
uninvolved_ptr
unmanaged_ptr
unowned_ptr
untainted_ptr
unyoked_ptr
virgin_ptr
visiting_ptr
watch_ptr
watcher_ptr
watching_ptr
witless_ptr
witness_ptr
Not sure any of those are right. (I like borrowed_ptr, but that would
suggest you need to give it back somehow.) How about
for_temporary_use_only_ptr? :-)
Tony
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-10-05 20:01:17 UTC
Permalink
Post by Tony V E
Some of the other suggested names can be found in earlier drafts (N3740).
exempt_ptr
nonowning_ptr
ptr
There, I edited the list for you, those are the names I think are the candidates
for renaming. I suppose it should be non_owning_ptr instead of nonowning_ptr.
Post by Tony V E
Not sure any of those are right. (I like borrowed_ptr, but that would
Seems as if they are better than observer_ptr.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Tony V E
2015-10-05 20:09:09 UTC
Permalink
On Mon, Oct 5, 2015 at 4:01 PM, Ville Voutilainen <
Post by Ville Voutilainen
Post by Tony V E
Some of the other suggested names can be found in earlier drafts (N3740).
exempt_ptr
nonowning_ptr
ptr
ptr has speech ambiguity.
Post by Ville Voutilainen
There, I edited the list for you, those are the names I think are the candidates
for renaming. I suppose it should be non_owning_ptr instead of
nonowning_ptr.
Post by Tony V E
Not sure any of those are right. (I like borrowed_ptr, but that would
Seems as if they are better than observer_ptr.
agreed.


assumed_ptr?

You are making assumptions about lifetime, and also "to take upon oneself",
"to take on", etc (secondary definitions of 'assume')

Tony
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-10-05 20:11:59 UTC
Permalink
Post by Tony V E
On Mon, Oct 5, 2015 at 4:01 PM, Ville Voutilainen
Post by Tony V E
Some of the other suggested names can be found in earlier drafts (N3740).
exempt_ptr
nonowning_ptr
ptr
ptr has speech ambiguity.
No it doesn't, it lives inside a namespace. As does std::function. Or std::bind.
And tons of other things.
Post by Tony V E
assumed_ptr?
You are making assumptions about lifetime, and also "to take upon oneself",
"to take on", etc (secondary definitions of 'assume')
It doesn't fit the theme of unique/shared/weak, and the name doesn't hint at
what exactly is assumed.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Michael Hamilton
2015-10-05 23:03:57 UTC
Permalink
Why not dumb_ptr, or basic_ptr?

Honestly.
Post by Ville Voutilainen
Post by Tony V E
On Mon, Oct 5, 2015 at 4:01 PM, Ville Voutilainen
Post by Tony V E
Some of the other suggested names can be found in earlier drafts (N3740).
exempt_ptr
nonowning_ptr
ptr
ptr has speech ambiguity.
No it doesn't, it lives inside a namespace. As does std::function. Or std::bind.
And tons of other things.
Post by Tony V E
assumed_ptr?
You are making assumptions about lifetime, and also "to take upon
oneself",
Post by Tony V E
"to take on", etc (secondary definitions of 'assume')
It doesn't fit the theme of unique/shared/weak, and the name doesn't hint at
what exactly is assumed.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Sam Kellett
2015-10-06 08:21:55 UTC
Permalink
Post by Michael Hamilton
Why not dumb_ptr, or basic_ptr?
Honestly.
or 'ptr' :p
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
David Rodríguez Ibeas
2015-10-06 12:30:40 UTC
Permalink
Avoiding the bikeshedding of the names, the use case mentioned in the
original message, that of a unique_ptr and a potential observer_ptr is not
really that simple. For the observer to know that the lifetime of the
object has died one of two things need to happen:

- the owning pointer notifies all observers
- the observing pointer can test that the owner is still around

Having the owning pointer notify all observers implies holding a data
structure and updating it as observers come and go, potentially in a
multithreaded environment. Clearly unique_ptr cannot do it now, and it
would be a non-trivial piece of machinery to maintain. An intrusive double
linked list could go part of the way, but you'd need to make it thread-safe.

The observing pointer cannot test whether the owner has died by looking
directly into the owner (which would be undefined behavior if the original
had died), so the information needs to be kept aside. The only sensible
place would be on dynamically allocated memory where the owning pointer
(not unique_ptr, but something possibly close to it) would write a "DEAD"
mark and the observers can test. At this point you are hitting the same
costs as shared_ptr: dynamic allocation, atomic variables.

The atomics would be needed for two purposes: one thread destroys the
owner, the mark needs to be made available to other threads that might want
to test whether their observers are alive; the last of the owner and
observers to die needs to cleanup the dynamically allocated memory.

On top of that the design is inherently flawed in that single ownership is
not an option. If a thread picks up an observer_ptr, it can know whether
the object has already died, but unless it can claim part-ownership on the
lifetime of the managed object it cannot use it for anything. The thread
using the observer needs to "lock" the object to ensure that it does not
die while it is being accessed.

This particular niche is well covered by shared_ptr in the standard,
although must admit that I have seen other approaches, like observers
obtaining access grants to the object and the owner blocking until the last
observer accessing the object goes away... the difference with using a
shared_ptr/weak_ptr combination is that there is a single owner and that
when the owner goes away it might need to wait, but knows for a fact that
no other thread will be touching this object from now on.

David
Post by Sam Kellett
Post by Michael Hamilton
Why not dumb_ptr, or basic_ptr?
Honestly.
or 'ptr' :p
--
---
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Nicol Bolas
2015-10-06 13:24:50 UTC
Permalink
On Tuesday, October 6, 2015 at 8:30:43 AM UTC-4, David Rodríguez Ibeas
Post by David Rodríguez Ibeas
Avoiding the bikeshedding of the names, the use case mentioned in the
original message, that of a unique_ptr and a potential observer_ptr is not
really that simple. For the observer to know that the lifetime of the
Nobody is suggesting that `observer_ptr` or whatever it is named will
automatically know that it's user has died. Just as `gsl::owner` does not
magically know how to destroy it.

Both of these types are nothing more than semantic annotations of your
code. `observer_ptr` means "I am not to delete this." `gsl::owner` means "I
must delete this." When a user reads those types, they can see if they're
being used correctly. Whereas with a naked `T*`, it is not at all clear
whether or not it should be deleted.

That is all.

Personally, I prefer annotating owning pointers rather than observers (so
if readers see `T*`, it's always an observer).
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-10-06 13:27:11 UTC
Permalink
Post by Nicol Bolas
Post by David Rodríguez Ibeas
Avoiding the bikeshedding of the names, the use case mentioned in the
original message, that of a unique_ptr and a potential observer_ptr is not
really that simple. For the observer to know that the lifetime of the
Nobody is suggesting that `observer_ptr` or whatever it is named will
automatically know that it's user has died.
You mean nobody but the original poster?
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Michael Hamilton
2015-10-07 22:47:28 UTC
Permalink
Well, one possibility is to utilize a custom deleter with the relevant
state. May require locking depending on how thread-safe you
want: https://ideone.com/MbKfRJ

The issue with shared_ptr and weak_ptr for this concept are several:

a) there is no "notification" of expiration, this is because shared_ptr is
meant to extend lifespan and it's an edge case and a bit of a hack to
utilize weak_ptr as a lifespan observer. You have to actively poll expired,
this may not be a dealbreaker, but it is a consideration.

b) the name shared_ptr implies ownership, which is not ideal when you have
a clearly defined intended ownership already and only want others to be
able to maintain handles to the stored value.

c) there is a possibility of multiple owners of shared_ptr which
artifically extend the lifespan of the object beyond what is safe or
intended. Even using weak_ptr, anybody using your stuff can .lock() and
create a shared_ptr instance thus extending the lifespan.

Shared_ptr is a possible source for cycles and is quite a bit more complex
than an observer_ptr could potentially be.

My implementation is surely flawed, but at least it shows one approach
which can be done without modifying the fundamental unique_ptr type at all.

On Tuesday, October 6, 2015 at 5:30:43 AM UTC-7, David Rodríguez Ibeas
Post by David Rodríguez Ibeas
Avoiding the bikeshedding of the names, the use case mentioned in the
original message, that of a unique_ptr and a potential observer_ptr is not
really that simple. For the observer to know that the lifetime of the
- the owning pointer notifies all observers
- the observing pointer can test that the owner is still around
Having the owning pointer notify all observers implies holding a data
structure and updating it as observers come and go, potentially in a
multithreaded environment. Clearly unique_ptr cannot do it now, and it
would be a non-trivial piece of machinery to maintain. An intrusive double
linked list could go part of the way, but you'd need to make it thread-safe.
The observing pointer cannot test whether the owner has died by looking
directly into the owner (which would be undefined behavior if the original
had died), so the information needs to be kept aside. The only sensible
place would be on dynamically allocated memory where the owning pointer
(not unique_ptr, but something possibly close to it) would write a "DEAD"
mark and the observers can test. At this point you are hitting the same
costs as shared_ptr: dynamic allocation, atomic variables.
The atomics would be needed for two purposes: one thread destroys the
owner, the mark needs to be made available to other threads that might want
to test whether their observers are alive; the last of the owner and
observers to die needs to cleanup the dynamically allocated memory.
On top of that the design is inherently flawed in that single ownership is
not an option. If a thread picks up an observer_ptr, it can know whether
the object has already died, but unless it can claim part-ownership on the
lifetime of the managed object it cannot use it for anything. The thread
using the observer needs to "lock" the object to ensure that it does not
die while it is being accessed.
This particular niche is well covered by shared_ptr in the standard,
although must admit that I have seen other approaches, like observers
obtaining access grants to the object and the owner blocking until the last
observer accessing the object goes away... the difference with using a
shared_ptr/weak_ptr combination is that there is a single owner and that
when the owner goes away it might need to wait, but knows for a fact that
no other thread will be touching this object from now on.
David
Post by Sam Kellett
Post by Michael Hamilton
Why not dumb_ptr, or basic_ptr?
Honestly.
or 'ptr' :p
--
---
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Michael Hamilton
2015-10-07 22:58:20 UTC
Permalink
I would like to clarify, however, this is getting into "new proposal using
a name another potential proposal was trying to use". I think it's a fine
direction to take a discussion starting with the name "observer_ptr", and
is on-topic. But there are two clear unrelated issues:

1) observer_ptr as proposed in the originally referenced article is badly
named. I don't care what it ends up being named, but it needs to be
something that doesn't imply an observer pattern.

2) With the above acknowledged, a proper observer_ptr could plug an
existing gap of avoiding dangling pointers by allowing explicit viewing of
lifespan while not interfering with the lifespan. Simply doing what it
says on the tin: observing an existing pointer with a single explicit owner.
Post by Michael Hamilton
Well, one possibility is to utilize a custom deleter with the relevant
https://ideone.com/MbKfRJ
a) there is no "notification" of expiration, this is because shared_ptr is
meant to extend lifespan and it's an edge case and a bit of a hack to
utilize weak_ptr as a lifespan observer. You have to actively poll expired,
this may not be a dealbreaker, but it is a consideration.
b) the name shared_ptr implies ownership, which is not ideal when you have
a clearly defined intended ownership already and only want others to be
able to maintain handles to the stored value.
c) there is a possibility of multiple owners of shared_ptr which
artifically extend the lifespan of the object beyond what is safe or
intended. Even using weak_ptr, anybody using your stuff can .lock() and
create a shared_ptr instance thus extending the lifespan.
Shared_ptr is a possible source for cycles and is quite a bit more complex
than an observer_ptr could potentially be.
My implementation is surely flawed, but at least it shows one approach
which can be done without modifying the fundamental unique_ptr type at all.
On Tuesday, October 6, 2015 at 5:30:43 AM UTC-7, David Rodríguez Ibeas
Post by David Rodríguez Ibeas
Avoiding the bikeshedding of the names, the use case mentioned in the
original message, that of a unique_ptr and a potential observer_ptr is not
really that simple. For the observer to know that the lifetime of the
- the owning pointer notifies all observers
- the observing pointer can test that the owner is still around
Having the owning pointer notify all observers implies holding a data
structure and updating it as observers come and go, potentially in a
multithreaded environment. Clearly unique_ptr cannot do it now, and it
would be a non-trivial piece of machinery to maintain. An intrusive double
linked list could go part of the way, but you'd need to make it thread-safe.
The observing pointer cannot test whether the owner has died by looking
directly into the owner (which would be undefined behavior if the original
had died), so the information needs to be kept aside. The only sensible
place would be on dynamically allocated memory where the owning pointer
(not unique_ptr, but something possibly close to it) would write a "DEAD"
mark and the observers can test. At this point you are hitting the same
costs as shared_ptr: dynamic allocation, atomic variables.
The atomics would be needed for two purposes: one thread destroys the
owner, the mark needs to be made available to other threads that might want
to test whether their observers are alive; the last of the owner and
observers to die needs to cleanup the dynamically allocated memory.
On top of that the design is inherently flawed in that single ownership
is not an option. If a thread picks up an observer_ptr, it can know whether
the object has already died, but unless it can claim part-ownership on the
lifetime of the managed object it cannot use it for anything. The thread
using the observer needs to "lock" the object to ensure that it does not
die while it is being accessed.
This particular niche is well covered by shared_ptr in the standard,
although must admit that I have seen other approaches, like observers
obtaining access grants to the object and the owner blocking until the last
observer accessing the object goes away... the difference with using a
shared_ptr/weak_ptr combination is that there is a single owner and that
when the owner goes away it might need to wait, but knows for a fact that
no other thread will be touching this object from now on.
David
Post by Sam Kellett
Post by Michael Hamilton
Why not dumb_ptr, or basic_ptr?
Honestly.
or 'ptr' :p
--
---
You received this message because you are subscribed to the Google
Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Michael Hamilton
2015-10-07 23:16:19 UTC
Permalink
One oddity with my code sample above, it seems if I move the uniquePtrValue
to a new one any copied observer beyond this point loses track of the
deleter. I'm guessing the address changes, but I'm not sure if there's a
good work-around. This may indicate a copy constructor for the observer
isn't possible with the current implementation of unique_ptr, but I haven't
investigated deeply:

int main() {
auto uniquePtrValue = make_observable_unique<int>(5);
auto observer = make_observer(uniquePtrValue, [](int *value) { std::cout <<
"We're destroying it! " << *value << std::endl; });
std::cout << (!observer.expired() ? *observer : -1) << std::endl;
auto movedUniquePtr = std::move(uniquePtrValue);
std::cout << (!observer.expired() ? *observer : -1) << std::endl;
auto observer2 = observer;
movedUniquePtr.reset();
std::cout << (!observer2.expired() ? *observer2 : -1) << std::endl;
std::cout << (!observer.expired() ? *observer : -1) << std::endl;
return 0;
}
Post by Michael Hamilton
I would like to clarify, however, this is getting into "new proposal using
a name another potential proposal was trying to use". I think it's a fine
direction to take a discussion starting with the name "observer_ptr", and
1) observer_ptr as proposed in the originally referenced article is badly
named. I don't care what it ends up being named, but it needs to be
something that doesn't imply an observer pattern.
2) With the above acknowledged, a proper observer_ptr could plug an
existing gap of avoiding dangling pointers by allowing explicit viewing of
lifespan while not interfering with the lifespan. Simply doing what it
says on the tin: observing an existing pointer with a single explicit owner.
Post by Michael Hamilton
Well, one possibility is to utilize a custom deleter with the relevant
https://ideone.com/MbKfRJ
a) there is no "notification" of expiration, this is because shared_ptr
is meant to extend lifespan and it's an edge case and a bit of a hack to
utilize weak_ptr as a lifespan observer. You have to actively poll expired,
this may not be a dealbreaker, but it is a consideration.
b) the name shared_ptr implies ownership, which is not ideal when you
have a clearly defined intended ownership already and only want others to
be able to maintain handles to the stored value.
c) there is a possibility of multiple owners of shared_ptr which
artifically extend the lifespan of the object beyond what is safe or
intended. Even using weak_ptr, anybody using your stuff can .lock() and
create a shared_ptr instance thus extending the lifespan.
Shared_ptr is a possible source for cycles and is quite a bit more
complex than an observer_ptr could potentially be.
My implementation is surely flawed, but at least it shows one approach
which can be done without modifying the fundamental unique_ptr type at all.
On Tuesday, October 6, 2015 at 5:30:43 AM UTC-7, David Rodríguez Ibeas
Post by David Rodríguez Ibeas
Avoiding the bikeshedding of the names, the use case mentioned in the
original message, that of a unique_ptr and a potential observer_ptr is not
really that simple. For the observer to know that the lifetime of the
- the owning pointer notifies all observers
- the observing pointer can test that the owner is still around
Having the owning pointer notify all observers implies holding a data
structure and updating it as observers come and go, potentially in a
multithreaded environment. Clearly unique_ptr cannot do it now, and it
would be a non-trivial piece of machinery to maintain. An intrusive double
linked list could go part of the way, but you'd need to make it thread-safe.
The observing pointer cannot test whether the owner has died by looking
directly into the owner (which would be undefined behavior if the original
had died), so the information needs to be kept aside. The only sensible
place would be on dynamically allocated memory where the owning pointer
(not unique_ptr, but something possibly close to it) would write a "DEAD"
mark and the observers can test. At this point you are hitting the same
costs as shared_ptr: dynamic allocation, atomic variables.
The atomics would be needed for two purposes: one thread destroys the
owner, the mark needs to be made available to other threads that might want
to test whether their observers are alive; the last of the owner and
observers to die needs to cleanup the dynamically allocated memory.
On top of that the design is inherently flawed in that single ownership
is not an option. If a thread picks up an observer_ptr, it can know whether
the object has already died, but unless it can claim part-ownership on the
lifetime of the managed object it cannot use it for anything. The thread
using the observer needs to "lock" the object to ensure that it does not
die while it is being accessed.
This particular niche is well covered by shared_ptr in the standard,
although must admit that I have seen other approaches, like observers
obtaining access grants to the object and the owner blocking until the last
observer accessing the object goes away... the difference with using a
shared_ptr/weak_ptr combination is that there is a single owner and that
when the owner goes away it might need to wait, but knows for a fact that
no other thread will be touching this object from now on.
David
Post by Sam Kellett
Post by Michael Hamilton
Why not dumb_ptr, or basic_ptr?
Honestly.
or 'ptr' :p
--
---
You received this message because you are subscribed to the Google
Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Nicol Bolas
2015-10-08 12:46:07 UTC
Permalink
Post by Michael Hamilton
Well, one possibility is to utilize a custom deleter with the relevant
https://ideone.com/MbKfRJ
Locking? In 2015? And *manual* locking for thread safety?

What do we look like, cavemen ;)

Sorry, but I would consider your implementation a non-starter on thread
safety grounds alone.

That's why weak_ptr explicitly requires shared_ptr in order to work.
Because sooner or later, the user of the weak_ptr needs to access the
actual pointer. And to prevent races on the pointer being deleted, you have
to ensure that the pointer is not destroyed while you're using it. Hence
weak_ptr::lock() returning a shared_ptr, which extends the lifetime of the
object.

Yes, it's annoying to have to use shared_ptr ownership just to have
handles. But it works, and it is *necessary*. As shown by your
implementation which very much does not work (or rather, works only in
single-threaded cases, which is the same thing).
Post by Michael Hamilton
a) there is no "notification" of expiration, this is because shared_ptr is
meant to extend lifespan and it's an edge case and a bit of a hack to
utilize weak_ptr as a lifespan observer. You have to actively poll expired,
this may not be a dealbreaker, but it is a consideration.
I don't see weak_ptr's usage as a handle as being a "hack" at all. That's
clearly a valid usage pattern, based on its API (as much as Stroustrup et.
al. keep talking about cycles and so forth).

b) the name shared_ptr implies ownership, which is not ideal when you have
Post by Michael Hamilton
a clearly defined intended ownership already and only want others to be
able to maintain handles to the stored value.
See threading above.
Post by Michael Hamilton
c) there is a possibility of multiple owners of shared_ptr which
artifically extend the lifespan of the object beyond what is safe or
intended. Even using weak_ptr, anybody using your stuff can .lock() and
create a shared_ptr instance thus extending the lifespan.
See threading above.
Post by Michael Hamilton
Shared_ptr is a possible source for cycles and is quite a bit more complex
than an observer_ptr could potentially be.
Your observer_ptr has to have an ever-increasing array of observer
functions. shared_ptr just has a single allocated block of memory.

The complexity is *far less* with shared/weak_ptr than with yours. Not to
mention the overhead.
Post by Michael Hamilton
My implementation is surely flawed, but at least it shows one approach
which can be done without modifying the fundamental unique_ptr type at all.
Technically, with the right kind of deleter, you could re-implement most of
shared_ptr in unique_ptr.

That doesn't make it a good idea.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-10-08 13:08:46 UTC
Permalink
Post by Nicol Bolas
Technically, with the right kind of deleter, you could re-implement most of
shared_ptr in unique_ptr.
That doesn't make it a good idea.
Not that it's proposing re-implementing most of shared_ptr, just the polymorphic
part, but http://open-std.org/JTC1/SC22/WG21/docs/papers/2014/n3974.pdf is
proposing a polymorphic unique_ptr.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Michael Hamilton
2015-10-08 18:44:11 UTC
Permalink
Thinking it through further, unique_ptr is not really possible to be thread
safe, and pointers to a single ownership item are not inherently thread
safe either, or possible to make thread safe in a comprehensive manner.

What *can* be done is locking the observer/notify list interaction in terms
of hooking up new observers and the deletion of the unique_ptr so that you
can't accidentally cause issues when hooking up new observers or
accidentally hook up an observer after the notify loop, but before the
delete.

This doesn't address the issue of accessing the observer_ptr for regular
use. You could make a observer_ptr::expired method work, but you could
never lock ownership (ie weak_ptr::lock) because single ownership semantics
don't make sense to persist the lifespan of a variable it doesn't own.

This really isn't a dealbreaker though. Consider what we are modeling
here! In the wild you'd have the notify list inside the parent object
rather than inside the pointer owning it, then when working on multiple
threads with pointers to the original the thread safety would be left as an
exercise for the user.

This is inherent to the problem. The real question is: what level of
threadsafety can you guarantee with the task you are trying to accomplish.

In the case of single ownership, I would argue it is the container holding
the reference to the unique_ptr which needs to be synchronized and *not*
the unique_ptr itself.

shared_ptr has the luxury of an undefined lifespan within the pointer
itself, unique_ptr is a single point of reference and is no more thead-safe
than a raw pointer owned by an object which would delete it in the
destructor.
Post by Nicol Bolas
Post by Michael Hamilton
Well, one possibility is to utilize a custom deleter with the relevant
https://ideone.com/MbKfRJ
Locking? In 2015? And *manual* locking for thread safety?
What do we look like, cavemen ;)
Sorry, but I would consider your implementation a non-starter on thread
safety grounds alone.
That's why weak_ptr explicitly requires shared_ptr in order to work.
Because sooner or later, the user of the weak_ptr needs to access the
actual pointer. And to prevent races on the pointer being deleted, you have
to ensure that the pointer is not destroyed while you're using it. Hence
weak_ptr::lock() returning a shared_ptr, which extends the lifetime of the
object.
Yes, it's annoying to have to use shared_ptr ownership just to have
handles. But it works, and it is *necessary*. As shown by your
implementation which very much does not work (or rather, works only in
single-threaded cases, which is the same thing).
Post by Michael Hamilton
a) there is no "notification" of expiration, this is because shared_ptr
is meant to extend lifespan and it's an edge case and a bit of a hack to
utilize weak_ptr as a lifespan observer. You have to actively poll expired,
this may not be a dealbreaker, but it is a consideration.
I don't see weak_ptr's usage as a handle as being a "hack" at all. That's
clearly a valid usage pattern, based on its API (as much as Stroustrup et.
al. keep talking about cycles and so forth).
b) the name shared_ptr implies ownership, which is not ideal when you have
Post by Michael Hamilton
a clearly defined intended ownership already and only want others to be
able to maintain handles to the stored value.
See threading above.
Post by Michael Hamilton
c) there is a possibility of multiple owners of shared_ptr which
artifically extend the lifespan of the object beyond what is safe or
intended. Even using weak_ptr, anybody using your stuff can .lock() and
create a shared_ptr instance thus extending the lifespan.
See threading above.
Post by Michael Hamilton
Shared_ptr is a possible source for cycles and is quite a bit more
complex than an observer_ptr could potentially be.
Your observer_ptr has to have an ever-increasing array of observer
functions. shared_ptr just has a single allocated block of memory.
The complexity is *far less* with shared/weak_ptr than with yours. Not to
mention the overhead.
Post by Michael Hamilton
My implementation is surely flawed, but at least it shows one approach
which can be done without modifying the fundamental unique_ptr type at all.
Technically, with the right kind of deleter, you could re-implement most
of shared_ptr in unique_ptr.
That doesn't make it a good idea.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Michael Hamilton
2015-10-08 18:47:40 UTC
Permalink
I kind of arrived at this by thinking about what an observer_ptr::lock
would have to do... Temporarily steal ownership of the unique_ptr and block
it from deleting unless the unique_ptr itself loses scope? Trying to
address this problem obviously degrades into a shared_ptr.

There are issues with shared_ptr from a user lifespan perspective which are
different from the issues of a unique_ptr, and I think any observation of a
unique_ptr's lifespan needs to occur at a higher level of the problem space
than the pointer itself can safely manage.
Post by Michael Hamilton
Thinking it through further, unique_ptr is not really possible to be
thread safe, and pointers to a single ownership item are not inherently
thread safe either, or possible to make thread safe in a comprehensive
manner.
What *can* be done is locking the observer/notify list interaction in
terms of hooking up new observers and the deletion of the unique_ptr so
that you can't accidentally cause issues when hooking up new observers or
accidentally hook up an observer after the notify loop, but before the
delete.
This doesn't address the issue of accessing the observer_ptr for regular
use. You could make a observer_ptr::expired method work, but you could
never lock ownership (ie weak_ptr::lock) because single ownership semantics
don't make sense to persist the lifespan of a variable it doesn't own.
This really isn't a dealbreaker though. Consider what we are modeling
here! In the wild you'd have the notify list inside the parent object
rather than inside the pointer owning it, then when working on multiple
threads with pointers to the original the thread safety would be left as an
exercise for the user.
This is inherent to the problem. The real question is: what level of
threadsafety can you guarantee with the task you are trying to accomplish.
In the case of single ownership, I would argue it is the container holding
the reference to the unique_ptr which needs to be synchronized and *not*
the unique_ptr itself.
shared_ptr has the luxury of an undefined lifespan within the pointer
itself, unique_ptr is a single point of reference and is no more thead-safe
than a raw pointer owned by an object which would delete it in the
destructor.
Post by Nicol Bolas
Post by Michael Hamilton
Well, one possibility is to utilize a custom deleter with the relevant
https://ideone.com/MbKfRJ
Locking? In 2015? And *manual* locking for thread safety?
What do we look like, cavemen ;)
Sorry, but I would consider your implementation a non-starter on thread
safety grounds alone.
That's why weak_ptr explicitly requires shared_ptr in order to work.
Because sooner or later, the user of the weak_ptr needs to access the
actual pointer. And to prevent races on the pointer being deleted, you have
to ensure that the pointer is not destroyed while you're using it. Hence
weak_ptr::lock() returning a shared_ptr, which extends the lifetime of the
object.
Yes, it's annoying to have to use shared_ptr ownership just to have
handles. But it works, and it is *necessary*. As shown by your
implementation which very much does not work (or rather, works only in
single-threaded cases, which is the same thing).
Post by Michael Hamilton
a) there is no "notification" of expiration, this is because shared_ptr
is meant to extend lifespan and it's an edge case and a bit of a hack to
utilize weak_ptr as a lifespan observer. You have to actively poll expired,
this may not be a dealbreaker, but it is a consideration.
I don't see weak_ptr's usage as a handle as being a "hack" at all. That's
clearly a valid usage pattern, based on its API (as much as Stroustrup et.
al. keep talking about cycles and so forth).
b) the name shared_ptr implies ownership, which is not ideal when you
Post by Michael Hamilton
have a clearly defined intended ownership already and only want others to
be able to maintain handles to the stored value.
See threading above.
Post by Michael Hamilton
c) there is a possibility of multiple owners of shared_ptr which
artifically extend the lifespan of the object beyond what is safe or
intended. Even using weak_ptr, anybody using your stuff can .lock() and
create a shared_ptr instance thus extending the lifespan.
See threading above.
Post by Michael Hamilton
Shared_ptr is a possible source for cycles and is quite a bit more
complex than an observer_ptr could potentially be.
Your observer_ptr has to have an ever-increasing array of observer
functions. shared_ptr just has a single allocated block of memory.
The complexity is *far less* with shared/weak_ptr than with yours. Not
to mention the overhead.
Post by Michael Hamilton
My implementation is surely flawed, but at least it shows one approach
which can be done without modifying the fundamental unique_ptr type at all.
Technically, with the right kind of deleter, you could re-implement most
of shared_ptr in unique_ptr.
That doesn't make it a good idea.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Tony V E
2015-10-08 19:35:45 UTC
Permalink
Post by Michael Hamilton
Thinking it through further, unique_ptr is not really possible to be
thread safe, and pointers to a single ownership item are not inherently
thread safe either, or possible to make thread safe in a comprehensive
manner.
What *can* be done is locking the observer/notify list interaction in
terms of hooking up new observers and the deletion of the unique_ptr so
that you can't accidentally cause issues when hooking up new observers or
accidentally hook up an observer after the notify loop, but before the
delete.
Locking the observer list (in any observer pattern) is surprisingly hard
(to do correctly). I see it done wrong so often (more often wrong than
right) that I'm thinking of doing a talk on this.

The problem isn't so much racing with deletion (you can claim that's the
user's fault). The problem is with the observer modifying the list while
observing.

Your choices are:

- potentially deadlocking (you should never hold a lock and call unknown
code, like an observer or a virtual function)
- having your observer called after you removed it
- an event system that can help you (this is how some observer patterns
work, often by "luck" I think)
- complicated (but correct) observer-list management code
Post by Michael Hamilton
This doesn't address the issue of accessing the observer_ptr for regular
use. You could make a observer_ptr::expired method work, but you could
never lock ownership (ie weak_ptr::lock) because single ownership semantics
don't make sense to persist the lifespan of a variable it doesn't own.
This is the crux of the problem. First, in concurrency, sharing (whether
via shared_ptr or globals or raw pointers, etc) is bad. Sharing implies
CPU/thread communication, which is slow. Second, why use a pointer that
you aren't sure exists. Either build an ownership model (like shared_ptr),
or just assume that the pointer won't go away - ie it is the user's problem
to ensure it (ie dumb_ptr). Don't conflate the observer pattern with
lifetime issues. Make them orthogonal. Maybe that's what you are saying
Post by Michael Hamilton
This really isn't a dealbreaker though. Consider what we are modeling
here! In the wild you'd have the notify list inside the parent object
rather than inside the pointer owning it, then when working on multiple
threads with pointers to the original the thread safety would be left as an
exercise for the user.
This is inherent to the problem. The real question is: what level of
threadsafety can you guarantee with the task you are trying to accomplish.
In the case of single ownership, I would argue it is the container holding
the reference to the unique_ptr which needs to be synchronized and *not*
the unique_ptr itself.
shared_ptr has the luxury of an undefined lifespan within the pointer
itself, unique_ptr is a single point of reference and is no more thead-safe
than a raw pointer owned by an object which would delete it in the
destructor.
Post by Nicol Bolas
Post by Michael Hamilton
Well, one possibility is to utilize a custom deleter with the relevant
https://ideone.com/MbKfRJ
Locking? In 2015? And *manual* locking for thread safety?
What do we look like, cavemen ;)
Sorry, but I would consider your implementation a non-starter on thread
safety grounds alone.
That's why weak_ptr explicitly requires shared_ptr in order to work.
Because sooner or later, the user of the weak_ptr needs to access the
actual pointer. And to prevent races on the pointer being deleted, you have
to ensure that the pointer is not destroyed while you're using it. Hence
weak_ptr::lock() returning a shared_ptr, which extends the lifetime of the
object.
Yes, it's annoying to have to use shared_ptr ownership just to have
handles. But it works, and it is *necessary*. As shown by your
implementation which very much does not work (or rather, works only in
single-threaded cases, which is the same thing).
Post by Michael Hamilton
a) there is no "notification" of expiration, this is because shared_ptr
is meant to extend lifespan and it's an edge case and a bit of a hack to
utilize weak_ptr as a lifespan observer. You have to actively poll expired,
this may not be a dealbreaker, but it is a consideration.
I don't see weak_ptr's usage as a handle as being a "hack" at all. That's
clearly a valid usage pattern, based on its API (as much as Stroustrup et.
al. keep talking about cycles and so forth).
b) the name shared_ptr implies ownership, which is not ideal when you
Post by Michael Hamilton
have a clearly defined intended ownership already and only want others to
be able to maintain handles to the stored value.
See threading above.
Post by Michael Hamilton
c) there is a possibility of multiple owners of shared_ptr which
artifically extend the lifespan of the object beyond what is safe or
intended. Even using weak_ptr, anybody using your stuff can .lock() and
create a shared_ptr instance thus extending the lifespan.
See threading above.
Post by Michael Hamilton
Shared_ptr is a possible source for cycles and is quite a bit more
complex than an observer_ptr could potentially be.
Your observer_ptr has to have an ever-increasing array of observer
functions. shared_ptr just has a single allocated block of memory.
The complexity is *far less* with shared/weak_ptr than with yours. Not
to mention the overhead.
Post by Michael Hamilton
My implementation is surely flawed, but at least it shows one approach
which can be done without modifying the fundamental unique_ptr type at all.
Technically, with the right kind of deleter, you could re-implement most
of shared_ptr in unique_ptr.
That doesn't make it a good idea.
--
---
You received this message because you are subscribed to the Google Groups
"ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an
Visit this group at
http://groups.google.com/a/isocpp.org/group/std-discussion/.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Tony V E
2015-10-06 19:36:41 UTC
Permalink
On Mon, Oct 5, 2015 at 4:11 PM, Ville Voutilainen <
Post by Ville Voutilainen
Post by Tony V E
On Mon, Oct 5, 2015 at 4:01 PM, Ville Voutilainen
Post by Tony V E
Some of the other suggested names can be found in earlier drafts (N3740).
exempt_ptr
nonowning_ptr
ptr
ptr has speech ambiguity.
No it doesn't, it lives inside a namespace. As does std::function. Or std::bind.
And tons of other things.
And when talking to developers, I need to say "use a standard function
here" instead of "use a function here" because one is ambiguous.

Do we want this pointer to be spoken of and known as the "standard pointer"
? (ie that's how I, and many, would pronounce "std::ptr"). And that still
sounds like I might mean a "normal pointer".

"bind" isn't a problem, because it doesn't have much other meaning in
coding. "you need to use bind here...". People tend to know what you mean;
no ambiguity.
Post by Ville Voutilainen
Post by Tony V E
assumed_ptr?
You are making assumptions about lifetime, and also "to take upon
oneself",
Post by Tony V E
"to take on", etc (secondary definitions of 'assume')
It doesn't fit the theme of unique/shared/weak, and the name doesn't hint at
what exactly is assumed.
So far all smart pointers are of the form XXX_ptr, where XXX describes
ownership/lifetime rules. So you at least have a hint - ownership/lifetime
is assumed.

exempt_ptr is the same - exempt from what? from ownership/lifetime.

I'm not against 'exempt' (it is probably my favorite of your list) - it
falls under the naming category of "I don't quite know what it means, but I
won't guess the meaning incorrectly, I'll look it up" (ie LACK of
understanding is better than MISunderstanding)


What happened to view_ptr - I thought you liked that one? (At least in the
hotel bar in Urbana.) Just curious. Potential confusion with other uses
of 'view'?

Tony
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-10-07 18:32:21 UTC
Permalink
Post by Ville Voutilainen
Post by Tony V E
ptr has speech ambiguity.
No it doesn't, it lives inside a namespace. As does std::function. Or std::bind.
And tons of other things.
And when talking to developers, I need to say "use a standard function here"
instead of "use a function here" because one is ambiguous.
Yes? Which part of that is a problem, or a problem that is not easily
surmountable?
Post by Ville Voutilainen
Post by Tony V E
assumed_ptr?
You are making assumptions about lifetime, and also "to take upon oneself",
"to take on", etc (secondary definitions of 'assume')
It doesn't fit the theme of unique/shared/weak, and the name doesn't hint at
what exactly is assumed.
So far all smart pointers are of the form XXX_ptr, where XXX describes
ownership/lifetime rules. So you at least have a hint - ownership/lifetime
is assumed.
exempt_ptr is the same - exempt from what? from ownership/lifetime.
Indeed, exempt from ownership. What does "assumed ownership" mean?
That sounds like it's giving exactly the wrong message, since that type
most certainly doesn't assume the ownership of anything.
What happened to view_ptr - I thought you liked that one? (At least in the
hotel bar in Urbana.) Just curious. Potential confusion with other uses of
'view'?
It doesn't do what the other views do, and it doesn't fit the
ownership-vocabulary.
Amongst unique/shared/weak, view seems like an oddball.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Tony V E
2015-10-07 18:50:19 UTC
Permalink
On Wed, Oct 7, 2015 at 2:32 PM, Ville Voutilainen <
Post by Ville Voutilainen
Post by Tony V E
Post by Ville Voutilainen
Post by Tony V E
ptr has speech ambiguity.
No it doesn't, it lives inside a namespace. As does std::function. Or std::bind.
And tons of other things.
And when talking to developers, I need to say "use a standard function
here"
Post by Tony V E
instead of "use a function here" because one is ambiguous.
Yes? Which part of that is a problem,
Only an annoyance. If you forget to be explicit, it can lead to confusion.
Post by Ville Voutilainen
or a problem that is not easily
surmountable?
For std::ptr, the part you took out :-) "standard pointer" is still
ambiguous with a normal/raw pointer, and I wouldn't want anyone to think
that it is the "standard" or typical pointer they should use.
Post by Ville Voutilainen
Post by Tony V E
Post by Ville Voutilainen
Post by Tony V E
assumed_ptr?
You are making assumptions about lifetime, and also "to take upon oneself",
"to take on", etc (secondary definitions of 'assume')
It doesn't fit the theme of unique/shared/weak, and the name doesn't
hint
Post by Tony V E
Post by Ville Voutilainen
at
what exactly is assumed.
So far all smart pointers are of the form XXX_ptr, where XXX describes
ownership/lifetime rules. So you at least have a hint -
ownership/lifetime
Post by Tony V E
is assumed.
exempt_ptr is the same - exempt from what? from ownership/lifetime.
Indeed, exempt from ownership. What does "assumed ownership" mean?
That sounds like it's giving exactly the wrong message, since that type
most certainly doesn't assume the ownership of anything.
Agreed.
Post by Ville Voutilainen
Post by Tony V E
What happened to view_ptr - I thought you liked that one? (At least in
the
Post by Tony V E
hotel bar in Urbana.) Just curious. Potential confusion with other
uses of
Post by Tony V E
'view'?
It doesn't do what the other views do, and it doesn't fit the
ownership-vocabulary.
Amongst unique/shared/weak, view seems like an oddball.
Agreed.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-10-07 18:56:23 UTC
Permalink
Post by Tony V E
Post by Ville Voutilainen
or a problem that is not easily
surmountable?
For std::ptr, the part you took out :-) "standard pointer" is still
ambiguous with a normal/raw pointer, and I wouldn't want anyone to think
that it is the "standard" or typical pointer they should use.
Fair point - std::ptr, while it supposedly nicely communicates the total
lack of ownership by totally lacking any description of it is perhaps too
terse compared to std::exempt_ptr or std::non_owning_ptr. The latter
are probably also better for people who don't want to see overuse of the
type. ;)
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Matthew Woehlke
2015-10-05 20:19:36 UTC
Permalink
Post by Tony V E
Walter (author of world's dumbest smart pointer) has basically said he
doesn't want to deal with naming anymore. If someone wants a better name,
they should write a proposal.
simple_ptr
basic_ptr
leased_ptr
loaned_ptr (I've "lent" you the object, but you don't own it)
remote_ptr
Post by Tony V E
How about for_temporary_use_only_ptr? :-)
Oh, dear...

for_your_eyes_only_ptr
this_ptr_will_self_destruct

... /me gdrlh
--
Matthew
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Ville Voutilainen
2015-10-05 20:22:17 UTC
Permalink
Post by Tony V E
Post by Tony V E
Walter (author of world's dumbest smart pointer) has basically said he
doesn't want to deal with naming anymore. If someone wants a better name,
they should write a proposal.
simple_ptr
basic_ptr
leased_ptr
loaned_ptr (I've "lent" you the object, but you don't own it)
remote_ptr
It'll take more than a listing of names to convince me that the set of
possibilities
should grow any larger than the set of three I listed. Each of those
names has a reasonable
rationale, and that's what I'll put forward into a paper when the time is right.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Matthew Woehlke
2015-10-05 21:01:32 UTC
Permalink
Post by Ville Voutilainen
Post by Tony V E
Post by Tony V E
Walter (author of world's dumbest smart pointer) has basically said he
doesn't want to deal with naming anymore. If someone wants a better name,
they should write a proposal.
simple_ptr
basic_ptr
leased_ptr
loaned_ptr (I've "lent" you the object, but you don't own it)
remote_ptr
It'll take more than a listing of names to convince me that the set of
possibilities
should grow any larger than the set of three I listed. Each of those
names has a reasonable
rationale, and that's what I'll put forward into a paper when the time is right.
That's fine. The point was to offer suggestions; if you don't like them,
you are free to reject them :-).

I do still think that leased_ptr / loaned_ptr may be worth
considering... the semantics are similar to non_owning_ptr, which is
probably my favorite otherwise, but 1½ syllables vs. 3 (not counting
"pointer", common to both).

(FWIW, I don't like exempt_ptr... exempt from what?)
--
Matthew
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.
Bjorn Reese
2018-06-07 08:48:55 UTC
Permalink
maybe uncared_ptr?
Or guest_ptr or shadow_ptr.
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
k***@gmail.com
2018-06-15 16:23:19 UTC
Permalink
I was actually here for another reason but... I love a good bikeshed :)

Is std::ptr or std::raw_ptr really ambiguous?

"Hey jack, I love what you have done with your raw pointer".

vs:

"Hey jack, I love what you have done with your ess tee dee raw pointer".

For example, when mentioning shared_ptr, it is feasible that someone could
turn round to me and say

"As in refer to two different 32-bit memory locations within one 64-bit
pointer variable? You are strange!"
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Tony V E
2018-06-15 17:47:59 UTC
Permalink
Post by k***@gmail.com
I was actually here for another reason but... I love a good bikeshed :)
Is std::ptr or std::raw_ptr really ambiguous?
yes.
Post by k***@gmail.com
"Hey jack, I love what you have done with your raw pointer".
"Hey jack, I love what you have done with your ess tee dee raw pointer".
This is sometimes necessary with std::function vs "function". But I'd
prefer not to need to disambiguate.
Post by k***@gmail.com
For example, when mentioning shared_ptr, it is feasible that someone could
turn round to me and say
"As in refer to two different 32-bit memory locations within one 64-bit
pointer variable? You are strange!"
:-)

It is only ambiguous when both interpretations are common.
Just to add to the naming list:

lax_ptr.

ie relaxed, lackadaisical, etc.
--
Be seeing you,
Tony
--
---
You received this message because you are subscribed to the Google Groups "ISO C++ Standard - Discussion" group.
To unsubscribe from this group and stop receiving emails from it, send an email to std-discussion+***@isocpp.org.
To post to this group, send email to std-***@isocpp.org.
Visit this group at https://groups.google.com/a/isocpp.org/group/std-discussion/.
Continue reading on narkive:
Loading...