Okay, you know that assertions can be a good idea. Under MFC, they can be made even more useful.
If it is meaningful for a programmer to have a favorite statement, then certainly my favorite statement is assert. It has gotten me out of trouble more often and more quickly than any other single technique I've used. So naturally I want to use assertions in Windows programs as well as generic C/C++ programs. MFC defines several types of assertions, which, for reasons I explain below, can be inappropriate for some applications. In this article I present a few improvements to the MFC assertions. A couple are rewrites of MFC's ASSERT and VERIFY, and a couple are useful extensions.
A lot has been said and written about assertions, but I keep finding C and C++ programmers who either don't know what they are or just end up not using them. For the former group, I list a few good books and magazine articles at the end of this article. If you are in the latter group, I hope this article will make it easier to start using assertions in your own programs. You have nothing to lose but hours of frustration and crashing programs.
Improving ASSERT and VERIFY
MFC (Microsoft Foundation Classes) defines two main forms of assertions for debugging purposes: ASSERT and VERIFY [1]. ASSERT is similar to the Standard C library's assert. It effectively disappears whenever NDEBUG is defined, which means it won't have any effect in release builds. VERIFY is a form of assertion that supports more succint coding. Whereas ASSERT statements disappear in release builds, the expression within a VERIFY statement is still evaluated if NDEBUG is defined. However, if NDEBUG is defined, VERIFY does not call the underlying assert if the condition fails.
VERIFY is defined as follows:
#ifdef NDEBUG #define VERIFY(exp) ((void)(exp)) #else #define VERIFY(exp) ASSERT(exp) #endifIt allows you to rewrite code like
BOOL Success = CreateConnection(/*...*/); ASSERT(Success);as:
VERIFY(CreateConnection(/*...*/));Assertions like these are very useful, but I never use MFC's ASSERT and VERIFY because they have three problems:
1) They are not defined as expressions; they are expanded instead in a do-while statement (see afx.h).
2) They interrupt the execution of the program and require the user to respond.
3) They don't log the assertion information, they just display it.
I think it is important that assertions be defined as expressions, because expressions can go in many more places than do-while statements can. Also, it is probably not hard to imagine situations when 2) and 3) above become undesirable. So I have written my own versions of ASSERT and VERIFY. They behave similarly to MFC's, but without the problems listed above.
These assertions are defined in Figure 1. The new ASSERT uses the function GioAssertFailedLine (Figure 2), which logs an assertion to a file and issues a warning, but does not issue a message box. If you want to stop program execution when an assertion fires while debugging, just put a breakpoint inside the GioAssertFailedLine function. This function, and all the assertions, are available with this month's online sources (see p. 3 for downloading instructions).
Let's be SAFE
Besides the standard assertion macros, I've made up a couple extra types, for the simple reason that I am lazy. Here is my favorite macro:
#ifdef NDEBUG #define SAFE(exp) (exp) #else #define SAFE(exp) (ASSERT(exp),(exp)) #endifThis macro has a big advantage over the more traditional forms of the assertions: it maintains the value and the type of the expression within it! This is no minor advantage, because it dramatically increases the number of places it can be used.
You do pay a price for this flexibility, but only in debugging builds. The price is that, thanks to the comma operator, the expression within the SAFE macro is evaluated twice. This macro could also be a source of errors if you fail to obey a rule similar to the one for ASSERT. What you put inside ASSERT must not have side effects or change the state of your program. With SAFE, the rule is somewhat less stringent: the expression inside the SAFE statement can have side effects as long as the same side effects result whether the expression is evaluated once or twice. Since the expression within SAFE is evaluated twice in debugging builds but only once in release builds, any difference in side effects could generate errors. You might rename SAFE into SAFE2 if it helps you to remember this.
Note that SAFE as defined above can be used only if ASSERT is defined as an expression. If it is not, like in MFC, you can define SAFE thus:
#ifdef NDEBUG #define SAFE(exp) (exp) #else #define SAFE(exp) (!(exp) && (AfxAssertFailedLine(THIS_FILE,__LINE__),0),(exp)) #endifIf you want to use the Microsoft C library's _assert, use the following:
#ifdef NDEBUG #define SAFE(exp) (exp) #else #define SAFE(exp) (!(exp) && (_assert(#exp,__FILE__,__LINE__),0),(exp)) #endifNote that SAFE does not replace ASSERT or VERIFY; it integrates them.
Using SAFE
Below I show how you can use SAFE, considering class CWnd to be defined as follows:
class CWnd { //...... int GetWindowText(char * pBuff, int len); };MFC programmers will recognize the two member functions defined in MFC's CWnd class. But those of you who don't use MFC still have enough information to understand the examples that follow:
BOOL TestFunction(CWnd * pWnd, char * pBuff, int TotalSize, int SizeElem) { if (!SAFE(pWnd)->GetWindowText(SAFE(pBuff), TotalSize/SAFE(SizeElem)) return SAFE(FALSE); else return TRUE; }This is a bit of a drastic use of SAFE four calls in one statement but it gives you an idea of all the places you can use it. The first SAFE statement asserts that the pointer to the window, pWnd, is not NULL before dereferencing it. The second asserts that the pointer to the buffer pBuff is not NULL before passing it to GetWindowText. The third is in a numeric expression where the denominator must not be zero. The fourth asserts before returning FALSE. The alternative without SAFE would have been:
BOOL TestFunction(CWnd * pWnd, char * pBuff, int TotalSize, int SizeElem) { ASSERT(pWnd); ASSERT(pBuff); ASSERT(SizeElem); if (!pWnd->GetWindowText(pBuff,TotalSize/SizeElem) { ASSERT(FALSE); return FALSE; } else return TRUE; }You might think that combining the first three assertions above would be a convenience:
ASSERT(pWnd && pBuff && SizeElem);Resist the temptation! If the above assertion fires, you will not know why; it could be any one or more of the three conditions!
From the above examples, you can see why I said I made SAFE up because I am lazy. You can use SAFE right there where you need it, without adding any extra statements. You can use it within any expression, to assert that parts of it will be non-zero. There is even a way to do the above example in only one line of code (broken into more than one line only to fit on this page):
BOOL TestFunction(CWnd * pWnd, char * pBuff, int TotalSize, int SizeElem) { return SAFE(SAFE(pWnd)-> GetWindowText(SAFE(pBuff), TotalSize/SAFE(SizeElem))); }But this is a bit much, and it's not a good idea to put a SAFE macro inside another. (pWnd will be evaluated four times in debugging builds.) Still, this example shows the flexibility of this macro.
Be careful when you use SAFE. Remember that the expression within SAFE is evaluated twice. Given a list of windows with a member function like this:
class CWindowList { //... CWnd * GetNextWindow() const; };this usage would be very wrong:
CWindowList MyWindowList; //... SAFE(MyWindowList.GetNextWindow())-> SetWindowText("Hello");In debugging code, GetNextWindow would be called inside an assert statement, returning the pointer to a window; then it would be called again returning the pointer to the next window, and this pointer would not be asserted before being dereferenced. So this code would fail twice: it would skip a window and it would not assert the pointer it was going to dereference.
The following code fixes this problem:
CWnd * pWnd= MyWindowList.GetNextWindow(); SAFE(pWnd)->SetWindowText("Hello");I must also stress the fact that the above assertion is meaningful only if you assume pWnd can never be NULL; that is, if it is NULL, it's because there is a bug in your program, and SAFE will tell you exactly where it is before your program crashes. But if pWnd's being NULL is a valid condition, then code like the following would be more appropriate:
CWnd * pWnd= MyWindowList.GetNextWindow(); If (pWnd) pWnd->SetWindowText("Hello");Remember that assertions don't fix the problem, they just point directly to it (and give you confidence when they don't scream at you for some time).
Other Types
I've made up some more types of assertions, but these represent just a few of the types you could possibly make for yourself. They are all based upon the good old ASSERT (see Figure 1):
ASSERT_LEN ASSERT_ONCE ASSERT_ARRAYINDEXASSERT_LEN asserts that you did not overrun a buffer; ASSERT_ARRAYINDEX asserts that the index of an array is valid; and ASSERT_ONCE is used inside loops, to avoid an assertion from firing many times once is enough to indicate a problem.
Conclusion
I put together the above assertions for use with MFC, but the ideas presented should be applicable in other environments. I know that C++ fans (like me) frown at the use of macros. If you can make something similar, perhaps using templates, without the use of macros, I'd be interested to know.
Notes and References
[1] MFC also defines many other specialized forms, such as ASSERT_POINTER, ASSERT_VALID, ASSERT_KINDOF, etc. Some of these more specialized forms work only with pointers to objects derived from some MFC classes. They are all useful, but the most important forms are ASSERT and VERIFY.
[2] Steve Maguire. Writing Solid Code (Microsoft Press, 1993).
[3] David Thielen. NO BUGS!: Delivering Error-Free Code in C and C++ (Addison-Wesley, 1992).
[4] Earl Fong. "Being Assertive in C/C++," C/C++ Users Journal, June 1997.
[5] Chuck Allison. "Code Capsules: The Standard C Library, Part 2," C/C++ Users Journal,, February 1995.
Giovanni Bavestrelli lives in Milan and is a software engineer for Techint S.p.A., Castellanza, Italy. He has a degree in Electronic Engineering from the Politecnico di Milano, and writes automation software for Pomini Roll Grinding machines. He has been working in C++ under Windows since 1992, specializing in the design and development of reusable object-oriented libraries. He can be reached at giovanni.bavestrelli@pomini.it.