Method 1 - AssertionsPros: Relatively fast, imposes no overhead in a release build, extremely simple to code. Cons: Slows the code down a little bit in a debug build, provide no safety in a release build, requires clients to read source code when they debug. ExplanationAn assertion is a boolean expression that must hold true in order for the program to continue to execute properly. You state an assertion in C++ by using the assert function, and passing it the expression that must be true: assert( this ); If this is zero, then the assert function stops program execution, displays a message informing you that "assert( this )" failed on such and such a line in your source file, and lets you go from there. If this wasn't zero, assert will simply return and your program will continue to execute normally. Note that the assert function does nothing and imposes no overhead in a release build, so do NOT use it like this: FILE* p = 0; assert( p = fopen("myfile.txt", "r+") ); ...because fopen will not be called in the release version! This is the correct way to do it: FILE* p = fopen("myfile.txt", "r+") ); assert( p ); ExamplesThese are best used in writing new code, where assumptions must almost always be made. Consider the following function: void sort(int* const myarray) // an overly simple example { for( unsigned int x = 0; x < sizeof(myarray)-1; x++ ) if( myarray[x] < myarray[y] ) swap(myarray[x], myarray[y]); } Count the number of assumptions this function makes. Now take a look at the better version, which makes debugging a bit easier: void sort_array(int* const myarray) { assert( myarray ); assert( sizeof(myarray) > sizeof(int*) ); for( unsigned int x = 0; x < sizeof(myarray)-1; x++ ) if( myarray[x] < myarray[y] ) swap(myarray[x], myarray[y]); } You see, that innocent-looking algorithm won't work if:
Although that is a simple algorithm, many functions you will write and/or encounter will be much larger and more complex than that. It is surprising when you see the amount of conditions which can cause a piece of code to fail. Let's take a look at a portion of an alpha-blending routine that I was playing with a while back: void blend(const video::memory& source, video::memory& destination, const float colors[3]) { // The algorithm used is: B = A * alpha const unsigned int width = source.width(); const unsigned int height = source.height(); const unsigned int depth = source.depth(); const unsigned int pitch = source.pitch(); switch( depth ) { case 15: // ... break; case 16: // ... break; case 24: { unsigned int offset = 0; unsigned int index = 0; for( unsigned int y = 0; y < height; y++ ) { offset = y * pitch; for( unsigned int x = destination.get_width(); x > 0; x-- ) { index = (x * 3) + offset; destination[index + 0] = source[index + 0] * colors[0]; destination[index + 1] = source[index + 1] * colors[1]; destination[index + 2] = source[index + 2] * colors[2]; } } } break; case 32: // ... break; } } Do you realize the amount of assumptions that function makes in the name of optimization? Let's try listing them: assert( source.locked() and destination.locked() ); assert( source.width() == destination.width() ); assert( source.height() == destination.height() ); assert( source.depth() == destination.depth() ); assert( source.pitch() == destination.pitch() ); assert( source.depth() == 15 or source.depth() == 16 or source.depth() == 24 or source.depth() == 32 ); Typically, the more you optimize in low-level code such as that, the more assumptions you make. My function requires that the source and destination video memory be locked (so multiple blend functions can be called with a single lock()/unlock()), and that they both have the same width, height, depth, and pitch. Placing these assertions at the top of the function will prevent some programmer in the future from wondering why my function doesn't work or causes access violations - all requirements are now stated clearly at the top of the function. However, you have to be careful which version of assert you use. If you use the ANSI C assert (defined in <cassert>), then you may wind up with a very large debug build because of all the string constants it creates. If you find this to be a problem, override assert to trigger an exception or something else instead of building the string constants. Also, you don't have to check every parameter and condition - some things are painfully obvious to a good programmer. Sometimes comments might be better because they do not increase the size of the final build. Good code should not require a plethora of assertions at the top of each function. If you find that you are writing a class and you have to place assertions in each member function to test the state, etc. then it is probably better to split the class up into other classes. ConclusionGetting into the habit of sprinkling assertions throughout your code has the following benefits:
|