Literally, since this thread started I had a tiny issue where I broke a class apart and the Juce-style “removeListener” didn’t make it across and I got an obscure failure in shutdown. The fact that it took me a minute to locate and fix because I’d spent time on this issue before didn’t make it a good thing!
chkn, you are quite right about “ScopedListener” - the only way to do it is to have the listener and broadcaster work together
“In almost all the listeners that I write, it’s a simple case of addListener() in the constructor, removeListener() in the destructor, so really a complete no-brainer.”
One could make the same argument against scoped pointers, eh?
It’s only a little work - but it’s work.
A connection to a broadcaster is a resource. Like most resources in C++, it’s best managed by Resource Acquisition Is Initialization.
You’ve seen my code - I write highly disciplined code, I like to think, but I write an awful lot of it and I want to write less and I want to be protected from having to spend a lot of time debugging a minor mistake like forgetting a destructor or in this case a removeListener. Other people are just mediocre programmers and should be protected from themselves to some appropriate extent between laiser-faire and a nanny state.
Knowing for a fact that there are no references left to my Listener once its destructor has completed, no matter what, makes me sleep better at night.
I write consumer code so my usage is perhaps different from yours but perhaps closer to a lot of your target audience.
For one thing, error handling makes up a surprisingly large amount of the body of the code - particularly around control structures like Listeners and Broadcasters. Even in my experiments on my own machine I’m constantly running into network dropouts, corrupt audio files, hitting cancel on dialogs, disks becoming unmounted and that sort of thing. As a result, I need to know that I can start to create any of my objects and then drop it on the floor at any point if there’s an error, without leaking any resources - like Broadcaster connections.
For another, since this is consumer software, there are a lot of small interface parts, so there are a large number of listeners and broadcasters, dozens of them, and I definitely hook them and unhook them at places that are not destructors.
Juce is one of the most RAII packages I know but I still found I had big destructors and issues in shutdown. When I went to enforcing my “Juce-derived objects must completely clean up after themselves” policy, my destructor for my biggest “ball of mud” class went from almost a page to two lines, and a nagging intermittent crash completely vanished. (That class is mostly gone now, thank Goodness, but these things are often unavoidable…)
APPENDIX: My generic listeners!
[code]#include
template class Broadcaster;
template
class Listener {
public:
typedef std::set<Broadcaster*> Broadcasters;
typedef typename Broadcasters::iterator iterator;
Listener() {}
virtual ~Listener();
virtual void operator()(Type x) = 0;
private:
Broadcasters broadcasters_;
friend class Broadcaster;
DISALLOW_COPY_AND_ASSIGN(Listener);
};
// Broadcast updates of type Type to a set of Listener.
template
class Broadcaster {
public:
typedef std::set<Listener*> Listeners;
typedef typename Listeners::iterator iterator;
Broadcaster() {}
virtual ~Broadcaster();
virtual void broadcast(Type x);
virtual void addListener(Listener* listener);
virtual void removeListener(Listener* listener);
protected:
CriticalSection lock_;
Listeners listeners_;
DISALLOW_COPY_AND_ASSIGN(Broadcaster);
};
template
Listener::~Listener() {
for (iterator i = broadcasters_.begin(); i != broadcasters_.end(); ++i)
(*i)->removeListener(this);
}
template
void Broadcaster::broadcast(Type x) {
ScopedLock l(lock_);
for (iterator i = listeners_.begin(); i != listeners_.end(); ++i)
(**i)(x);
}
template
Broadcaster::~Broadcaster() {
for (iterator i = listeners_.begin(); i != listeners_.end(); ++i)
(*i)->broadcasters_.erase(this);
}
template
void Broadcaster::addListener(Listener* listener) {
ScopedLock l(lock_);
listeners_.insert(listener);
listener->broadcasters_.insert(this);
}
template
void Broadcaster::removeListener(Listener* listener) {
ScopedLock l(lock_);
listeners_.erase(listener);
listener->broadcasters_.erase(this);
}
template
void add(Broadcaster* broadcaster, Listener* listener) {
broadcaster->addListener(listener);
}
template
void remove(Broadcaster* broadcaster, Listener* listener) {
broadcaster->removeListener(listener);
}
[/code]