#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
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 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?
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.
Why not dumb_ptr, or basic_ptr?Honestly.
--
---
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/.
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:
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.
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.
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.
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.
--
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 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
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!"