Most C/C++ programmers have likely made use of the __FILE__ and __LINE__ macros in their source files. They are provided by C/C++ compilers for outputting the file and line number of a particular line of code, usually for debugging purposes. I use this a lot with exceptions targeted at trapping bugs. For instance:
class BugException { public: BugException(const string& mm, const string& ff, int ll): msg(mm), file(ff), line(ll) {} const string msg; const string file; const int line; };
which I can use as:
void doSomething() { // ... fail some bug-trapping test throw BugException("i should be > j", __FILE__, __LINE__); } int main() { try { doSomething(); } catch (BugException& be) { cerr << "*** Bug: " << be.msg << " at file " << be.file << ", line " << be.line << endl; } }
One type of debugging info that I often wish the compiler provided is the name of the function or method to which a line of code belongs, something that might be called __FUNCTION__ :
class Foo { public: Void bar() { cout << "Inside method " << __FUNCTION__ << endl; } };
with the following output: "Inside method void Foo::bar()". This would be handy when one needs to verify, say, the calling order of some functions or methods, or to check that some methods are or are not called. It is not always practical to use debuggers since this most often requires tedious settings of breakpoints, button presses, etc. instead of just running the program in debug mode, and examining the output.
The only compiler that seems to offer this kind of macro is gcc. For those unfortunates among us who do not get to use open source compilers such as gcc, there is no way of getting the compiler to generate the output of __FUNCTION__ automatically. Instead, I have had to resort to tricks like
#include "Foo.h" void Foo::bar(int a) const { cout << "In method Foo::bar(int) const" << endl; // do stuff ... }
Replacing the Foo:: part of the debug output with a call to typeid(*this).name() does not improve the matter much: it is starting to look messy, requires even more typing, and it is not the class name that changes most often, but the method signature itself, including the method's name. I discovered the hard way that good will and vigilance were not enough: the debug info rapidly gets out of sync with the actual class interface, especially when the software is reviewed, extended or debugged. No big deal but has lead to some confusion.
After posting a question about this to the ACCU-general mailing list and getting several interesting answers, it was clear that there was no real support for this kind of debugging information, and I would have to design my own. But it was also clear, using bits and pieces mentioned in several posts, mixed in with a little OOD, that a pretty decent, if not perfect, solution was within reach.
The essence is to use typeid() on the function or method pointer, something first suggested by Jon Jagger. The pitfall is that you need to make a few substitutions in its output before you can really get something useful. The above example becomes (I define TypeID later)
// #include "Foo.h" void Foo::bar(int a) const { cout << "In method " << TypeID::fn(bar, "bar") << endl; // ... }
The onus is still on me to keep the second argument in sync with the function/method name (the compiler will notify me if the first one does not correspond to a function/method), but this is clearly far less work that having to worry about the whole function/method declaration. It is also fairly concise and clear.
There are many possible ways of implementing TypeID so I will only discuss one possibility. I implemented TypeID as a namespace that groups and provides type information functionality. I first implemented it as a class with static methods, with some of the methods as inline and private to hide some implementation details. However given the context of use (debugging), inlining is not really important, and I was hoping to avoid having to create temporary objects, so I ended up implementing it as a namespace . In this case, static methods are no longer necessary, (they become merely functions), neither are visibility qualifiers, and this probably reflects better the fact that no member data is involved.
The header file for TypeID is simply:
// file TypeID.hh namespace TypeID { template <typename T> std::string fn(const T& ptr, const std::string& name); }
whereas the source file hides the implementation details:
// file TypeID.cc #include <typeinfo> namespace TypeID { template <typename T> std::string fn(const T& fptr, const std::string& name) { std::string fsignature = typeid(fptr).name(); simplify(fsignature); return insertName(fsignature, name); } void simplify(std::string& fs) { // do simplifications like fs.replace("basic_string<...>", "string"); } std::string insertName( std::string& fsig, const std::string& fname); } std::string& TypeID::insertName(std::string& routine, const std::string& name){ int i = routine.find_first_of('('); if (i >= routine.size()) // probably // not a function return routine; routine.replace(i, 1, " "); i = routine.find_first_of('*', i); routine.replace(i, 1, name); i = routine.find_first_of(')'); return routine.erase(i, 1); }
The fn routine consists, quite simply, of three steps: get the signature of the function pointer passed as first argument; simplify it; and finally, insert the function name given as second argument. The latter is done by insertName , which has to find where to put the name in the signature. The rule I have used is to find the first '*' after the first left parenthesis. I really do not know how universal this is. Therefore, insertName may have to be modified slightly for different compilers (so maybe TypeID should be a class after all and the method made virtual ).
The simplification step is fairly important due to the very long names that can be produced when STL types appear as arguments to the function/method given as first parameter to TypeID::fn . Clearly, simplify will vary depending on your application, on personal preferences, and so forth. In the above code sample, I used a line of pseudocode to represent one possible type of simplification that could occur. Again, customizing the simplify routine might be a good enough reason to implement TypeID as a class.
Finally, it is interesting to note that no check is done on what is passed to TypeID::fn . It could be given a pointer to a method or an object, it would still chug away without complaining, but probably produce meaningless output. Again, given the context of use (debugging), worrying about this level of detail is probably overkill.
There are many ways that this could be extended or improved. Having a class with virtual functions for simplify and insertName would allow for easier customisation, though it would require creating an object. In addition, methods/routines could be added that split the function signature into several parts and makes each available separately. With a class instead of namespace, you could do things like:
// #include "Foo.h2" void Foo::bar(int a) const { static const TypeID_sgi method(bar, "bar"); cout << "In method " << method.name() << "\nMethod parameters " << method.params() << endl; // ... }
where TypeID_sgi is, hypothetically, a subclass of TypeID customised for SGI's MipsPro compiler.
If others have any suggestions for improvement or ideas for alternatives, I would be happy to hear from you.
Acknowledgements
I would like to thank all those who answered my email question on the ACCU-general mailing list, and in particular Jon Jagger's suggestion about typeid , which opened up some interesting possibilities.