std::observer_ptr poorly and misleadingly named

828 views
Skip to first unread message

Michael Hamilton

unread,
Oct 5, 2015, 3:19:11 PM10/5/15
to ISO C++ Standard - Discussion
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.


"The observer pattern is a software design pattern in which an object, 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."

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


Tony V E

unread,
Oct 5, 2015, 3:57:04 PM10/5/15
to std-dis...@isocpp.org
On Mon, Oct 5, 2015 at 3:19 PM, Michael Hamilton <max...@gmail.com> wrote:
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.


"The observer pattern is a software design pattern in which an object, 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."

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


Tony V E

unread,
Oct 5, 2015, 3:59:21 PM10/5/15
to std-dis...@isocpp.org
I'm all for co-opting terms.  At least I don't think there will be ambiguity with other uses.

Tony

Ville Voutilainen

unread,
Oct 5, 2015, 4:01:19 PM10/5/15
to std-dis...@isocpp.org
On 5 October 2015 at 22:57, Tony V E <tvan...@gmail.com> wrote:
> Some of the other suggested names can be found in earlier drafts (N3740).
> ie:
> 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.

> Not sure any of those are right. (I like borrowed_ptr, but that would

Seems as if they are better than observer_ptr.

Tony V E

unread,
Oct 5, 2015, 4:09:11 PM10/5/15
to std-dis...@isocpp.org
On Mon, Oct 5, 2015 at 4:01 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 5 October 2015 at 22:57, Tony V E <tvan...@gmail.com> wrote:
> Some of the other suggested names can be found in earlier drafts (N3740).
> ie:
>  exempt_ptr
> nonowning_ptr
> ptr

ptr has speech ambiguity.
 

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.

> 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


Ville Voutilainen

unread,
Oct 5, 2015, 4:12:01 PM10/5/15
to std-dis...@isocpp.org
On 5 October 2015 at 23:09, Tony V E <tvan...@gmail.com> wrote:
>
>
> On Mon, Oct 5, 2015 at 4:01 PM, Ville Voutilainen
> <ville.vo...@gmail.com> wrote:
>>
>> On 5 October 2015 at 22:57, Tony V E <tvan...@gmail.com> wrote:
>> > Some of the other suggested names can be found in earlier drafts
>> > (N3740).
>> > ie:
>> > 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.

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

Matthew Woehlke

unread,
Oct 5, 2015, 4:19:53 PM10/5/15
to std-dis...@isocpp.org
On 2015-10-05 15:57, Tony V E wrote:
> 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

> How about for_temporary_use_only_ptr? :-)

Oh, dear...

for_your_eyes_only_ptr
this_ptr_will_self_destruct

... /me gdrlh

--
Matthew

Ville Voutilainen

unread,
Oct 5, 2015, 4:22:20 PM10/5/15
to std-dis...@isocpp.org
On 5 October 2015 at 23:19, Matthew Woehlke <mwoehlk...@gmail.com> wrote:
> On 2015-10-05 15:57, Tony V E wrote:
>> 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.

Matthew Woehlke

unread,
Oct 5, 2015, 5:01:50 PM10/5/15
to std-dis...@isocpp.org
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

Michael Hamilton

unread,
Oct 5, 2015, 7:03:57 PM10/5/15
to ISO C++ Standard - Discussion
Why not dumb_ptr, or basic_ptr?

Honestly.

Sam Kellett

unread,
Oct 6, 2015, 4:21:59 AM10/6/15
to std-dis...@isocpp.org
On 6 October 2015 at 00:03, Michael Hamilton <max...@gmail.com> wrote:
Why not dumb_ptr, or basic_ptr?

Honestly.

or 'ptr' :p

David Rodríguez Ibeas

unread,
Oct 6, 2015, 8:30:43 AM10/6/15
to std-dis...@isocpp.org
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 

--

---
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-discussio...@isocpp.org.
To post to this group, send email to std-dis...@isocpp.org.
Visit this group at http://groups.google.com/a/isocpp.org/group/std-discussion/.

Nicol Bolas

unread,
Oct 6, 2015, 9:24:51 AM10/6/15
to ISO C++ Standard - Discussion, dib...@ieee.org
On Tuesday, October 6, 2015 at 8:30:43 AM UTC-4, David Rodríguez Ibeas wrote:
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:

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

Ville Voutilainen

unread,
Oct 6, 2015, 9:27:13 AM10/6/15
to std-dis...@isocpp.org
On 6 October 2015 at 16:24, Nicol Bolas <jmck...@gmail.com> wrote:
>> 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:
> 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?

Tony V E

unread,
Oct 6, 2015, 3:36:45 PM10/6/15
to std-dis...@isocpp.org
On Mon, Oct 5, 2015 at 4:11 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 5 October 2015 at 23:09, Tony V E <tvan...@gmail.com> wrote:
>
>
> On Mon, Oct 5, 2015 at 4:01 PM, Ville Voutilainen
> <ville.vo...@gmail.com> wrote:
>>
>> On 5 October 2015 at 22:57, Tony V E <tvan...@gmail.com> wrote:
>> > Some of the other suggested names can be found in earlier drafts
>> > (N3740).
>> > ie:
>> >  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.

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

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


Ville Voutilainen

unread,
Oct 7, 2015, 2:32:23 PM10/7/15
to std-dis...@isocpp.org
On 6 October 2015 at 22:36, Tony V E <tvan...@gmail.com> wrote:
>> > 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?

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

Tony V E

unread,
Oct 7, 2015, 2:50:25 PM10/7/15
to std-dis...@isocpp.org
On Wed, Oct 7, 2015 at 2:32 PM, Ville Voutilainen <ville.vo...@gmail.com> wrote:
On 6 October 2015 at 22:36, Tony V E <tvan...@gmail.com> wrote:
>> > 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,

Only an annoyance.  If you forget to be explicit, it can lead to confusion.
 
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.

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


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


Agreed.


Ville Voutilainen

unread,
Oct 7, 2015, 2:56:25 PM10/7/15
to std-dis...@isocpp.org
On 7 October 2015 at 21:50, Tony V E <tvan...@gmail.com> wrote:
>> 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. ;)

Michael Hamilton

unread,
Oct 7, 2015, 6:47:29 PM10/7/15
to ISO C++ Standard - Discussion, dib...@ieee.org
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.

Michael Hamilton

unread,
Oct 7, 2015, 6:58:20 PM10/7/15
to ISO C++ Standard - Discussion, dib...@ieee.org
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.

Michael Hamilton

unread,
Oct 7, 2015, 7:16:19 PM10/7/15
to ISO C++ Standard - Discussion, dib...@ieee.org
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;

Nicol Bolas

unread,
Oct 8, 2015, 8:46:07 AM10/8/15
to ISO C++ Standard - Discussion, dib...@ieee.org
On Wednesday, October 7, 2015 at 6:47:29 PM UTC-4, Michael Hamilton wrote:
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

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

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 a clearly defined intended ownership already and only want others to be able to maintain handles to the stored value.

See threading above.
 
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.
 
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.
 
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.

Ville Voutilainen

unread,
Oct 8, 2015, 9:08:48 AM10/8/15
to std-dis...@isocpp.org
On 8 October 2015 at 15:46, Nicol Bolas <jmck...@gmail.com> wrote:
> 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.

Michael Hamilton

unread,
Oct 8, 2015, 2:44:11 PM10/8/15
to ISO C++ Standard - Discussion, dib...@ieee.org
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.

Michael Hamilton

unread,
Oct 8, 2015, 2:47:40 PM10/8/15
to ISO C++ Standard - Discussion, dib...@ieee.org
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.

Tony V E

unread,
Oct 8, 2015, 3:35:48 PM10/8/15
to std-dis...@isocpp.org
On Thu, Oct 8, 2015 at 2:44 PM, Michael Hamilton <max...@gmail.com> wrote:
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

 
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 below:
 
 

--

zongshe...@gmail.com

unread,
Jun 6, 2018, 12:06:00 AM6/6/18
to ISO C++ Standard - Discussion
maybe uncared_ptr?

Michael Hamilton於 2015年10月6日星期二 UTC+8上午3時19分11秒寫道:
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.


"The observer pattern is a software design pattern in which an object, 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."

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


Bjorn Reese

unread,
Jun 7, 2018, 4:48:57 AM6/7/18
to std-dis...@isocpp.org
On 06/06/18 06:06, zongshe...@gmail.com wrote:
> maybe uncared_ptr?

Or guest_ptr or shadow_ptr.

karste...@gmail.com

unread,
Jun 15, 2018, 12:23:19 PM6/15/18
to ISO C++ Standard - Discussion
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!"

Tony V E

unread,
Jun 15, 2018, 1:48:01 PM6/15/18
to std-discussion
On Fri, Jun 15, 2018 at 12:23 PM, <karste...@gmail.com> wrote:
I was actually here for another reason but... I love a good bikeshed :)

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

yes.



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


This is sometimes necessary with std::function vs "function". But I'd prefer not to need to disambiguate.


 
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
Reply all
Reply to author
Forward
0 new messages