Nice C++ object oriented code basically means writing everything in objects/classes. However, not everything we need in programming is classes. This article concentrates on callback functions, and presents a C++ implementation of delegates which doesn’t use pointers to functions.
It’s not surprising that callback functions will usually look ugly in C++ code. Pointers to member functions are usually a mess. It is mainly because they need the “this” pointer as a parameter. Observe the following example:
class A {
public:
int func ();
};
...
int (A::*pmf)();
pmf = &A::func;
A a;
A *pa = &a;
(a.*pmf)();
C++ was meant to be nicer than this, wasn’t it?
If you’re familiar with C# programming, one of the new bright ideas in C# is “Delegates”. If you get to the bottom of it, delegates can actually be easily implemented in C++ using regular classes, as shown below.
As a rule of thumb, use the ideas of the following code whenever you want to implement a callback function in C++ and you won’t get disappointed.
Implementing a C++ delegates consist of a few simple tasks. The following example illustrates how to create and use a delegate of a function that takes string and returns void.
1. Declare a prototype of a “pointer to a function that takes string and returns void” as a pure virtual class with one member function:
class StringDelegate
{
public:
virtual void runTheFunction(string params) = 0;
};
2. Implement your specific callback function
The callback function’s implementation is a class that inherits from StringDelegate. Optionally, it would be nicer if it contained some kind of a “Runner” class that is responsible to handle the received data. The code follows:
class OurDelegate : public StringDelegate
{
public:
void runTheFunction(string data); // Implementation!
OurDelegate(Runner& runner); // The constructor should get the runner
private:
OurDelegate(); // No default constructor
Runner m_runner;
};
// The constructor
OurDelegate::OurDelegate(Runner& runner):m_runner(runner)
{
}
// The actual implementation
void OurDelegate::runTheFunction(string data)
{
m_runner.run(data);
}
Now we can write the code that's calling our "callback function":
void callme(StringDelegate sd)
{
sd.runTheFunction("Tralala");
}
Running the delegates is simple:
callme(OurDelegate(runner));
No pointers needed! Now that’s C++.
The following item is a must for every Visual C++ 6.0 programmer which uses operator new. The default behavior of operator new in Microsoft Visual C++ 6.0 in case of an error is to return NULL, in total contrast to the standard which explicitly states that new raises a bad_alloc exception upon failure. This behavior can lead to many unpleasant surprises, especially when trying to write portable code.
Here are some painful facts to feast your eyes with after you have written so much “error free” code with operator new in VC6:
How is it possible to write safe operator new code in VC6?
After reading knowledge base article 167733 [1] about this issue, it seems that there is a simple solution to the problem. First, define a new_handler function and order it to throw an exception on any failure:
#include <new>
#include <new.h>
#pragma init_seg(lib)
int my_new_handler(size_t) {
throw std::bad_alloc();
return 0;
}
struct my_new_handler_obj {
_PNH _old_new_handler;
int _old_new_mode;
_tag_g_new_handler_obj() {
_old_new_mode = _set_new_mode(1);
_old_new_handler = _set_new_handler(my_new_handler);
}
~_tag_g_new_handler_obj() {
_set_new_handler(_old_new_handler);
set_new_mode(_old_new_mode);
}
} _g_new_handler_obj;
Note: _set_new_handler’s syntax will not be explained in this item. However, the most important issue here is that my_new_handler will not be called unless you override the default behavior by calling set_new_mode(1) , which orders to alter the default behavior.
The plot thickens. Be aware that using new(std::nothrow) does not shield you from exceptions when using your own new_handler, and still raises an exception upon failure. I shall write it again - new(std::nothrow) throws an exception when using _set_new_handler. This means that every piece of code in your binary that rightfully assumed new(std::nothrow) doesn’t throw, will suddenly get exceptions - inner STL code, your own code, and third party code!
The anomaly is even worse than can be thought of - as it turns out in practice, in VC6, when linking the solution above by against the debug runtime libraries, std::nothrow behaves expectedly and does not throw an exception. However, when linking it against the release runtime libraries, operator new(std::nothrow) will throw an exception anyway!!
This irritating behavior in release configuration is a simple result of a compiler optimization that removes the try-catch statements around the new(std::nothrow) because of the reasonable assumption that std::nothrow should not, in theory, throw anything. When the compiler behaves according to the standard, and the runtime doesn’t, don’t be surprised to find a critical bug.
Needless to say, the worse thing about altering std::nothrow’s behavior is that the STL itself uses it itself in many implementations.
In order to save you from the std::nothrow inferno, article 167733 also suggests defining your own operator new with std::nothrow when using set_new_handler, as follows:
void *__cdecl operator new(size_t cb, const std::nothrow_t&) throw()
{
char *p;
try {
p = new char[cb];
}
catch (std::bad_alloc) {
p = 0;
}
return p;
}
The code above guarantees that new(std::nothrow) will never throw in your compiled module even if you have written a new_handler that throws, as required. Even when writing for high performance binaries, a few more op codes in your binary are worth the headaches of trying to understand why exceptions come out of nowhere.
Now your code should conform to the standard, even on VC6, isn’t it? Sadly enough, NOT EVER!
When using the runtime libraries as dlls, the new handler you have just defined also imposes itself on the global scope - on every other dll loaded in the program. The critical problem is that the operator new(std::nothrow) defined above is only recognized in the local compiled scope. Therefore, when an external dll you load uses new(std::nothrow) itself, its new(std::nothrow) will actually fire exceptions inside that dll (!). This is because on the one hand, the new_handler is global and defines throwing exceptions inside the runtime library’s malloc, and on the other, the external dll doesn’t link with your implementation of operator new(std::nothrow). Back to the bug’s equilibrium - Fixing it in one place just makes it pop up in another place.
Trying to wrestle with this horrifying bug could be very annoying when only after a few days you discover that it is a known issue and in the September 2003 issue of MSDN Magazine, James Haben (2) has published an article exactly about this surprising anomaly.
Haben concluded that the mismatch in Visual C++ 6.0 between operator new, operator new(std::nothrow), and the STL could not be completely resolved. He himself found an implementation of STL that doesn’t use std::nothrow and used it instead.
Even experienced programmers would be surprised how profound this bug can be. Unless you were aware of this anomaly, it probably affects almost every piece of C++ code you ever wrote in Visual Studio 6. However, as mentioned before, this bug is only relevant to those of us who really love VC6 and insist on working with it. Moving on to VS2003 or VS2005 completely frees you from all this nonsense, but be careful and make sure that the old code doesn’t rely on returning NULL when new fails.
The issue I’m going to describe is sort of a solution to an inherited problem in the MFC programming environment. According to the MFC architecture, all messages are handled synchronously. Of course, this issue makes no difference in the case that for every single message you want to process, the processing will take you only a short time.
Lengthy processing (lets say, more than half a second) of one of your messages might result in a poor functioning program. For example, if after the user of your application presses an OK button, it will take you more than a moment to decide what to do next, and the user won’t even be able to move the application window. This is because when you process your message (OK key press), you block the processing of all other messages in your application!
This could be even more horrible if your program does some sort of computation or network management task that might take even longer to finish. Another example is showing some kind of animation on your application window.
One standard solution to this problem might be to use multi-threading. This is actually a solution many programmers employ. However, we all know the disadvantages of multi-threaded programming - Employing such an approach will make your code uglier, and in some cases, you might even run into bugs just because of the fact that you used multi-threading.
A more elegant solution for this problem is written on the following lines:
void AsynchronousMessageProcessing() {
MSG msg;
while (::PeekMessage(&msg, NULL, NULL, NULL, PM_NOREMOVE)) {
AfxGetThread->PumpMessage();
}
}
As you might have noticed, this code simply handles all messages waiting in the message queue. A simple call to AsynchronousMessageProcessing() once in a while, when you do your lengthy processing, will solve the problem easily.
I have this function in my “Tools” library and use it whenever I can.
Powered by WordPress