Pete explains why macros are frowned upon in C++, and shows how to use C callbacks in C++.
To ask Pete a question about C or C++, send e-mail to pbecker@wpo.borland.com, use subject line: Questions and Answers, or write to Pete Becker, C/C++ Users Journal, 1601 W. 23rd St., Ste. 200, Lawrence, KS 66046.
Q
In your July column about code walkthroughs, you point out the beginner's error of using magic numbers to specify sizes of data structures:char buf[100]; money(i,buf,100);I've been a programmer for over 20 years and understand why such an approach is a Bad Thing. I've been using C for only a few years, however, and I don't understand the implied reluctance to using a #define to avoid the problem. Can you enlighten me and other relatively new C programmers like myself? -- Dan Beimers
A
The truth is, that reluctance stems from the added capabilities of C++ and some of the problems that arise from them. Using a #define is the usual answer in C, and it's a perfectly good one. I rarely do any straight C programming these days, except when I'm working on this column, and I sometimes forget my roots. Go ahead:#define BUFSIZE 100 char buf[BUFSIZE]; money(i,buf,BUFSIZE);One of the goals of C++ is to provide better encapsulation of names than C does. That's why C++ has member functions: you can use the same name for member functions of two different classes and not worry about the names colliding. For example:
class Circle { public: void draw(); }; class Gunfighter { public: void draw(); };In C you would have to give these two functions distinct names, something like this:
struct Circle; struct Gunfighter; void draw_circle( struct Circle * ); void draw_gun( struct Gunfighter * );After coding four or five of these, most programmers get tired of coming up with different names for things that ought to have the same name. That's the benefit of the encapsulation that C++ provides: you can usually give things the most appropriate names, without having to worry that some other piece of code has used that name for something else.
However, one big problem that remains in C++ is macros. They don't respect scopes, but stomp on anything that uses their name. Carrying our previous example further, here's what can happen:
#define draw 1 class Circle { public: void draw(); }; class Gunfighter { public: void draw(); };The preprocessor substitutes the character '1' for each of the function names draw in the two class definitions. Then the compiler complains that the source code doesn't make sense. That's why most programmers adopt the convention of using all uppercase letters in macro names: it makes them fairly distinctive, so mistakes like this are less likely to occur. In C++ we can go a step further and eliminate one of the most common uses of macros by using consts instead.
const int draw = 1; class Circle { public: void draw(); }; class Gunfighter { public: void draw(); };Now there is no conflict: the name draw in each of these classes is distinct from the name draw defined in the first line, and everything works just fine.
Now, you may think this example is a bit artificial. It's not! Problems like this occur daily. For example, consider a perfectly innocent class definition for use in a Windows program:
class Window { public: void TextOut( const char * ); };In a multi-module program this class definition can lead to mysterious linker problems, typically expressed in messages such as this:
Error: Unresolved external 'Window::TextOutA(const char *)' referenced from module test.cppAfter a great deal of hunting around, including a call to your local C++ guru, you finally discover that the system header file WINGDI.H contains the following lines:
#ifdef UNICODE #define TextOut TextOutW #else #define TextOut TextOutA #endif // !UNICODEAny translation unit that uses WINGDI.H and your Window class will see the name of your member function as TextOutW or TextOutA, not TextOut. Any translation unit that does not use WINGDI.H will see it as TextOut. That's why the linker complains: it doesn't know that these are supposed to be the same thing.
Unfortunately, this is not an isolated case. The Windows header files step on several hundred names with this technique. As the use of the name UNICODE suggests, the purpose of the #define is to call one set of functions when you don't want Unicode support, and a different set of functions when you do. This macro technique works fairly well in C, but it makes C++ programming much harder.
That's why I'm reluctant to use macros. I'm the C++ guru who gets the frantic calls from programmers trying to figure out why their programs won't link. I'd like to put myself out of business.
Q
I'm having a bit of trouble interfacing a C++ class to some numerical routines that aren't "class aware." It boils down to being able to cast a member function to a "classless" function with the same return type and argument types. Example:class foobar { public: double func(double); }Now suppose I have a library routine that wants a pointer to a function that returns a double and takes a double as an argument:
typedef double (*dfuncd)(double); void libfunc(dfuncd f);If I call this function using the class member I get an error saying that the cast from double (foobar::*)(double) to dfuncd cannot be performed. Is there a way around this? Is there a good reason why this can't be done? This isn't the first time I've run across this. I usually rewrite the library function to handle the member function type but in this case I would rather not do this.
Thanks in advance for your assistance.
-- Erik M. LowndesA
This is a fairly common problem that programmers run into when they need to mix C code with C++. The C standard library includes functions such as qsort, which takes a pointer to a function used to compare objects. GUI libraries use callback functions when something happens that requires handling by user code. Many libraries also use callback functions to notify users of errors detected in the library code. Since these libraries are typically written in C, they don't know anything about C++ objects, and have no way of handling member functions. You need to give them ordinary C functions. When you've got a C++ member function that does exactly what you need, how can you turn that member function into a C function?Many people's first reaction is to use a cast. As you've seen, that doesn't work; the compiler refuses to do it. There's a good reason for this: a member function that takes a double and returns a double is not the same as an ordinary function that takes a double and returns a double. That's because the member function operates on an object of its own class type. Most compilers implement this behavior by passing in a pointer to that object as an additional parameter to the member function. The member function func in your code snippet above actually gets called with a double and a pointer to an object of type foobar. But don't get your hopes up: you can't call it that way. At least, not without going through some non-portable and probably illegal contortions. That's definitely not recommended.
As usual, what I recommend is taking several steps back and getting a broader view of what it is that you're trying to accomplish. It's easy to get so far down in the details of what you're doing that you overlook something fundamental that will make your job much easier. What people often overlook in this situation is that you really don't need a member function, because you have no object to work on.
Let's take an error callback as an example. Suppose you're using a library that has a function to register a message handler. It has this prototype:
void register_message_handler( void (*msg)(int sev,const char *) );The documentation tells you that you use this function to register a callback function that will be called whenever the library detects an error. The callback function should take two parameters. The first is an integer that indicates the severity of the error, and the second is a text string describing the error. This function is used only for reporting the problem; the library itself will handle terminating the program if the error is severe.
Writing such a callback function in C++ is simple, provided you don't get too caught up in making things object oriented. After all, you're talking to C code, and C doesn't have the level of encapsulation that C++ does, so you're going to have to make some compromises. Here's a simple form of this callback:
extern "C" void show_error( int sev, const char *error ) { cerr << "Error (" << sev << ") " << error << endl; } int main() { register_message_handler( show_error ); // the rest of your code goes // here }Simple, isn't it? Let's see if we can't complicate it a bit. Suppose you don't want to restrict yourself to always writing this message to standard error (cerr), but want to be able to switch streams at any point in your program. If you're thinking in C++ you'll encapsulate the message handler in a class:
class MessageHandler { public: MessageHandler() : out(&cerr) {} void show_error( int sev, const char * error ); void assign_stream( ostream& str ) { out = &str; } private: ostream *out; }; void MessageHandler::show_error( int sev, const char *error) { *out << "Error (" << sev << ") " << error << endl; }This is the right way to do this in C++, but it gets you right into a bind: you can't call show_error from C code, because it isn't a C function. There's an easy way around this: write a global function that knows about a global message handler:
MessageHandler handler; extern "C" void show_error( int sev, const char *error ) { handler.show_error( sev, error ); } int main() { register_message_handler( show_error ); // the rest of your code // goes here }Now you can call handler.assign_stream to change the destination stream. The library doesn't know that it's talking to a C++ class.
The global function show_error is an example of what is known as a closure, that is, a simple function that binds an object and a member function. Closures can be very powerful. Perhaps a future version of C++ will support them directly instead of requiring you to write the function yourself.
Pete Becker is Senior Development Manager for C++ Quality Assurance at Borland International. He has been involved with C++ as a developer and manager at Borland for the past six years, and is Borland's principal representative to the ANSI/ISO C++ standardization committee.