People have asked me several times what I think of “functors” in C++, so I decided to form an opinion (and write it down somewhere that myself and others can refer to). I’ve also always meant to include more technical content in this blog…
My reference material on the subject was an article Chuck provided, Callbacks in C++ Using Template Functors. I felt that while it’s true that all of the functor alternatives shown in the article are obviously undesirable according to the criteria listed (and perhaps other more basic criteria, such as code readability), it doesn’t really seem to show an example of the interface-oriented (i.e. pure virtual class) model, which I believe is the purest way to solve the problem. (By pure, I mean satisfying criteria such as being object-oriented, type-safe, and non-coupled, yet not relying on void* casts and template wizardry.) Here is the example that I feel was omitted:
class Notifiable { public: virtual void notify() = 0; } class Button { public: void click() { if (m_pnOnClick) m_pnOnClick->notify(); } void onClick(Notifiable* pn) { m_pnOnClick = pn; } private: Notifiable* m_pnOnClick; } class CDPlayer { public: void play(); void stop(); } class CDPlayNotifiable : public Notifiable { public: CDPlayNotifiable(CDPlayer* cdp) : m_cdp(cdp) { } virtual void notify() { m_cdp->play(); } private: CDPlayer* m_cdp; } class CDStopNotifiable : public Notifiable { public: CDStopNotifiable(CDPlayer* cdp) : m_cdp(cdp) { } virtual void notify() { m_cdp->stop(); } private: CDPlayer* m_cdp; } void main() { CDPlayer cdp; CDPlayNotifiable pn(&cdp); CDStopNotifiable sn(&cdp); Button playButton; playButton.onClick(&pn); Button stopButton; stopButton.onClick(&sn); // ... }
I believe this satisfies all but one of the “Criteria for a Good Callback Mechanism” mentioned in the article: object-oriented, type-safe, non-coupling, non-type-intrusive, and flexible. The remaining criterion, being “generic”, is of questionable validity. Obviously it’s desirable not to require the user to write repetitive support code, but I think there’s some value to explicitly defining interfaces and being able to write custom binding code. Consider, for instance, how most UI frameworks include an argument on their events that identifies the sending object. If the functor of the Button class in the article included this argument, you could no longer directly bind it directly to CDPlayer::play().
However, the code above sucks in C++ for two reasons: 1) There’s no shorthand way to provide implementations of Notifiable. (Anonymous classes in Java are great for this.) 2) Managing the lifetime of the Notifiable objects is difficult. (Again, with GC in Java, this is not an issue.) In my example, I used pointers, which necessitates ensuring that the Notifiable objects exist at least as long as the Buttons referencing them. In the functor article, this is accomplished by the functors being held by value. This makes sense for functors, which are known to be small and non-polymorphic and to implement a default constructor and operator=(). Objects implementing Notifiable may or may not be small, but more importantly, they are polymorphic and therefore cannot be held by value.
Given these difficulties, functors do seem to be a reasonable mechanism for handling callbacks in C++. I’m curious whether the memcpy() on the pointer-to-member-function in FunctorBase is kosher with regard to the C++ standard, but even if it’s not, it’s the sort of thing that always tends to work anyway.
BTW, in Java, the above code directly translates to this, which seems pretty much ideal to me:
interface Notifiable { void notify(); } class Button { public void click() { if (m_onClick != null) m_onClick.notify(); } public void onClick(Notifiable n) { m_onClick = n; } private Notifiable m_onClick; } class CDPlayer { public void play(); public void stop(); } void main() { final CDPlayer cdp = new CDPlayer(); Button playButton = new Button(); playButton.onClick(new Notifiable() { public void notify() { cdp.play(); } }); Button stopButton = new Button(); stopButton.onClick(new Notifiable() { public void notify() { cdp.stop(); } }); // ... }
Well, it is obvious you stole from Java from the start, but as your last paragraph shows, it is your preferred mechanism anyway.
I like the pure-virtual methods as well. If, for the only reason, it seems easier to read to me than a bunch of memcpy/ptr magic-nonsense. It doesn’t feel like you are peeking under the covers, as memcpy and ptr wizardry does.
And, why not consider some merge of the solution? Nothing prevents one from using pure-interfaces to define the callbacks, and then implementing them via templates (if that makes sense for the implementation). Or, providing common support code as an Abstract base class that does some ‘common’ work. Again, ideas stolen from Java…
Concerning the lifetime… consider boost shared_ptr. Well, there’s a family of ptr managers in boost, and one could pick the appropriate one for their needs, but i’ve found shared_ptr to be the right thing in just about all cases.
all-in-all, death to c++.