Q1. Why are callback functions used?
Q2. Are callbacks evil? Fun for those who know, for others a nightmare.
Q3. Any alternative to callback?
views:
773answers:
8Callbacks are used for example to invoke aynchronous operations, that is, code that runs in a different thread while the caller follows its own way. You need some mechanism to know when the asyncrhonous operation has finished.
Why would them be evil? As with any other programming resource, they are useful when used judiciously. In fact the Windows API and the .NET framework, for example, use callbacks extensively.
Don't know about C++, but in the .NET world, syncronization objects and events are alternatives.
Callbacks decrease coupling - the invoked party is passed some pointer and it has no idea what's behind it. Callbacks are such a fortunate solution that they are very widespread.
For example look at sqlite3_exec(). You give it a query and optionally a callback. It executes the query and invokes the callback for each row as it is retrieved. Now it's SQLite's business to execute the query fast and with low resource consumtion and it's only your business to process the retrieved results as you like. You can add them to a container and process all later or you can process them immediately one-by-one or you can fax them somewhere and expect the other party to fax them back - SQLite doesn't care, it's completely abstracted and can just do its job.
Q1. Callbacks are needed if you use a layered approach in which higher levels call lowers and get feedback from lowers via callback.
Q2. When taken some precautions they're not worse than e.g. exceptions. In some ways, they're similar.
Q3. More coupling: lower lovels know higher.
Remarks:
- simple way (1 callback handler per callback): Register CallbackHandler objects via interfaces
- Use signals (QT, boost,...), and make sure that unique signals are used per callback to enhance traceability
Edit: example:
User calls ProtocolHandler to send a message and ProtocolHandler calls the user to send the reply: mutual dependency.
Layered: user is higher level, ProtocolHandler is lower level. On startup, it registers a callback for the reply and calls the ProtocolHandler to send a message. The ProtocolHandler uses the callback to send the reply: Only user is dependent of ProtocolHandler.
If we in C++ context, consider using (e.g. generalized callbacks).
The basic behaind idea is that callback (class method) can be of any name and the callback is not required to derive from some class knowned by callback executor.
The only restriction on callback is input arguments and return value. So this reduces couple to zero ... :)
UDP:
Answer of @EffoStaff Effo is an example where callback should be of the particular class (deriving) and have a fix name. All this "restrictions" are absent in context of generalize callbacks.
Regardless of "using callbacks in C++ increase coupling" or not, I suggest using event handler style especially process event-like things. For example, concrete a Design Pattern instead of callback concept as the below:
class MyClass
{
public:
virtual bool OnClick(...) = 0;
virtual bool OnKey(...) = 0;
virtual bool OnTimer(...) = 0;
virtual bool OnSorting(...) = 0
...
};
You may still consider the above functions are callbacks, but when considering them as the known Design Pattern, you won't get confused because you are doing OO and writing C++.
Effo UPD@2009nov13 - Typical cases: Framework, Events System or Concurrent Programming Model, and so on. Following examples should be useful.
Framework controls the overall flow as the Hollywood Principle states "Don't call us, we'll call you." (that's what "callback" means exactly) while per a normal function or lib, caller controls the flow.
A well-known C framework is Linux kernel and a Linux driver writer knows that s/he'd implement a "struct file_operations" in it "read()" means OnRead() and "write()" means OnWrite() etc.
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
...
};
But the simplest example of a framework should be:
The Framework | A developer to do
----------------------------+--------------------------------------
| class MyClass : public Actor
| {
| public:
pApplication->Init(...); | virtual bool OnInit(...) {}
pApplication->Run(...); | virtual int OnRun(...) {}
pApplication->Quit(...); | virtual void OnQuit(...) {}
| ...
| };
and pApplication->Init() will call pActor->OnInit, and pApplication->Run() calls pActor->OnRun(), and so on internally. Most Windows GUI developers had experienced implementing OnMouseClick() or OnButtonPress() etc.
I agree with other answers of this thread that they give correct explanation based on the viewpoint accordingly, such as handlers in layered approach, generalized callback or aynchronous operations, and so on. It's up to you that what idea(s) would be suitable for you.
In rare scenarios with Win32-style-callbacks watch out for "obscure reentrancy" issues (the following is irrelevant if by callback you just mean very basic passing of a function as arg to another function where concurrency issues are unimaginable ).
The excellent index to Joe Duffy's "Concurrent Programming on Windows" lists 5 topics under "reentrancy", which generally are specializations of the concept of "Around Robin-Hood's Barn RECURSION on the SAME THREAD" -- in other words from the top-rated-answer "the invoked party is passed some pointer and it has no idea what's behind it.", "no idea" can sometimes lead around-Robin-Hood's-barn.
Nothing I just said is specific to callbacks, but if the invoked-party stumbles across one of Duffy's scenarios then "obscure reentrancy" can happen. To put it a different way, implicit in the concept of "callback" is that you're going to seem to be called back in-the-same-thread, how does this happen, how does it get synchronized.
If you Google on Duffy's book-title and tack the word Callback onto your search then you get more than 10 pages of hits.
Q3. Any alternative to callback?
I prefer the functor form of callback. e.g. this sort of thing:
class WidgetContainerOrProcessorOfSomeSort
{
public:
struct IWidgetFunctor
{
virtual bool operator()( const Widget& thisWidget )=0;
};
....
bool ProcessWidgets( IWidgetFunctor& callback )
{
.....
bool continueIteration = callback( widget[ idxWidget ] );
if ( !continueIteration ) return false;
.....
}
};
struct ShowWidgets : public WidgetContainerOrProcessorOfSomeSort::IWidgetFunctor
{
virtual bool operator(){ const Widget& thisWidget }
{
thisWidget.print();
return true;
}
};
WidgetContainterOrProcessorOfSomeSort wc;
wc.ProcessWidgets( ShowWidgets() );
It always seems a bit verbose for simple samples, but in the real world I find it much easier than trying to remember exactly how to construct complex function pointer declarations :-)
- Callbacks decrease coupling as they allow you to write code that calls a function that can be changed
- Callbacks are not evil, just that if you are using raw function pointers things can get messy really fast.
- There isn't an alternative to callbacks per se, but there are alternatives to using raw function pointers.
A post pointing to Boost was made earlier for Boost.Function. If you are looking for a more generic solution for callbacks, such as multiple functions attached to the same callback or something like that, consider using Boost.Signals. The name comes from signals and slots, which is the way some people refer to callbacks nowadays, especially for GUIs.