Style Case Studies: Generic Callbacks - Provide a Helper
(Page 5 of 5 )
Guideline: Consider enabling polymorphism so that different instantiations of your class template can be used interchangeably, if that makes sense for your class template. If it does, do it by providing a common base class shared by all instantiations of the class template.
10. (Idiom, Tradeoff) There could be a helper make_callback function to aid in type deduction. After a while, users may get tired of explicitly specifying template parameters for temporary objects:
list< callback< Widget > > l;
l.push_back( callback<Widget>( w, &Widget::SomeFunc ) );
Why write Widget twice? Doesn’t the compiler know? Well, no, it doesn’t, but we can help it to know in contexts where only a temporary object like this is needed. Instead, we could provide a helper so that they need only type:
list< callback< Widget > > l;
l.push_back( make_callback( w, &Widget::SomeFunc ) );
This make_callback works just like the standard std::make_pair. The missing make_callback helper should be a function template, because that’s the only kind of template for which a compiler can deduce types. Here’s what the helper looks like:
template<typename T >
callback<T> make_callback( T& t, void (T::*f) () ) {
return callback<T>( t, f );
}
11. (Tradeoff) Add support for other callback signatures. I’ve left the biggest job for last. As the Bard might have put it, “There are more function signatures in heaven and earth, Horatio, than are dreamt of in your void (T::*F) ()!”
Guideline: Avoid limiting your template; avoid hardcoding for specific types or for less general types.
If enforcing that signature for callback functions is sufficient, then by all means stop right there. There’s no sense in complicating a design if we don’t need to—for complicate it we will, if we want to allow for more function signatures!
I won’t write out all the code, because it’s significantly tedious. (If you really want to see code this repetitive, or you’re having trouble with insomnia, see books and articles like [Alexandrescu01] for similar examples.) What I will do is briefly sketch the main things you’d have to support and how you’d have to support them:
First, what about const member functions? The easiest way to deal with this one is to provide a parallel callback that uses the const signature type, and in that version, remember to take and hold the T by reference or pointer to const.
Second, what about non-void return types? The simplest way to allow the return type to vary is by adding another template parameter.
Third, what about callback functions that take parameters? Again, add template parameters, remember to add parallel function parameters to operator(), and stir well. Remember to add a new template to handle each potential number of callback arguments.
Alas, the code explodes, and you have to do things like set artificial limits on the number of function parameters that callback supports. Perhaps in a future C++0x language we’ll have features like template “varargs” that will help to deal with this, but not today.
Summary
Putting it all together, and making some purely stylistic adjustments like using typename consistently and naming conventions and whitespace conventions that I happen to like better, here’s what we get:
class CallbackBase {
public:
virtual void operator()() const { };
virtual ~CallbackBase() = 0;
};
CallbackBase::~CallbackBase() { }
template<typename T>
class Callback : public CallbackBase {
public:
typedef void (T::*F)();
Callback( T& t, F f ) : t_(&t), f_(f) { }
void operator()() const { (t_->*f_)(); }
private:
T* t_;
F f_;
};
template<typename T>
Callback<T> make_callback( T& t, void (T::*f) () ) {
return Callback<T>( t, f );
}
This chapter is from Exceptional C++ Style, by Herb Sutter (ISBN 0201760428, copyright 2005. All rights reserved. It is reprinted with permission from Addison-Wesley Professional). Check it out at your favorite bookstore today.
Buy this book now. |
| DISCLAIMER: The content provided in this article is not warranted or guaranteed by Developer Shed, Inc. The content provided is intended for entertainment and/or educational purposes in order to introduce to the reader key ideas, concepts, and/or product reviews. As such it is incumbent upon the reader to employ real-world tactics for security and implementation of best practices. We are not liable for any negative consequences that may result from implementing any information covered in our articles or tutorials. If this is a hardware review, it is not recommended to open and/or modify your hardware. |