📄 chapter4.html
字号:
<br><br>Instead of type casting the right-hand side to the correct data type, why not try to type cast the left-hand side to a void pointer type? How can this be done? <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>_LPV() macro</b>#define _LPV(hObj) *(LPVOID FAR*)&hObj</pre></tr></td></table> <br>This _LPV() macro effectively changes the type of an l-value object to LPVOID. The danger in this macro is that it assumes the argument is a far pointer to an object. <br><br>We can now rewrite the NEWOBJ() and FREE() macros as follows. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>NEWOBJ() and FREE() implementation, final form</b>#define NEWOBJ(hObj) \ (_LPV(hObj)=FmNew(sizeof(*hObj),&_CD(hObj),szSRCFILE,__LINE__))#define FREE(hObj) (_LPV(hObj)=FmFree(hObj))</pre></tr></td></table> <br>Regardless of the type of hObj, it is forced into an LPVOID type so that an assignment can be made to hObj without compiler error or warning messages. <br><br><b>4.5.4 Summary</b> <br><br>The NEWOBJ() and FREE() macros work together to provide an abstraction layer on top of the heap manager code that allows objects to be created and destroyed.<br><a name="faulttolerant"><br></a><big><b>4.6 Fault-Tolerant Methods</b></big> <br><br>An important part of the object model presented in this chapter is that it allows us to write code that checks the validity of object handles passed to method functions at run-time. The <a href="#verify">VERIFY() syntax</a> allows for a block of code to be conditionally executed depending upon the validity of an object handle. This allows a certain degree of fault-tolerance to be built into code. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Protect the code of a method function in the body of a VERIFY() block. </tr></td></table></blockquote>If you are careful in how you design method functions, your program is able to withstand any number of faults, indicating programming errors or bugs, but your program remains running. <br><br>Without giving consideration to the fact that a method function may fail, a program may end up bombing anyway. So, how should method functions be designed to withstand faults? <br><br>A model that I use for designing method functions is to treat objects like state machines and methods as state machine transitions. <br><br><b>4.6.1 The State Machine Model</b> <br><br>A state machine consists of a number of valid states and ways to move from one valid state to another valid state. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Objects are state machines and methods are state machine transitions. </tr></td></table></blockquote>The important part to remember about this model is that an object instance is always in a valid state. What happens when a method function fails? <br><br>Consider an object that is in a valid state. We wish to execute a method function on the object. If the handle passed to the method function is valid, the method function executes and takes the object from one valid state to another valid state. If the handle passed to the method function is invalid, the method function does not execute and all objects in the heap stay in their current valid state. <br><br>What this means is that method functions must never leave the object in an invalid state. The subtle implication of this is that an object must never require more than one method function to be called to take the object from one valid state to another, because if the first method function call succeeds, but the second method function call fails, the object is left in an invalid state. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> When a method function fails due to an invalid handle, all objects in the heap stay in their current valid state. </tr></td></table></blockquote><b>4.6.2 Designing Method Functions</b> <br><br>Designing method functions to be fault-tolerant when the method function returns no information is a snap. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Fault-tolerant method function, no return information</b>void APIENTRY Method( HOBJECT hObject, (other arguments) ){ VERIFY(hObject) { (body of code) }} /* Method */</pre></tr></td></table> <br>Because the method function returns no information, making it fault-tolerant simply means enclosing the body of the method in a VERIFY() block. <br><br>How do you design a method function to fail gracefully when the method function returns information? At first, this may seem impossible, but in practice I have found it to be an easy task. <br><br>If the method function fails, setting the return information to a value that is reasonable is OK. If the method function is returning a simple numeric value and zero is a possible value, return zero. If a character buffer is being returned, return a null string or whatever string would be considered valid. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Fault-tolerant method function, with return information</b>TYPE APIENTRY Method( HOBJECT hObject, (other arguments) ){ TYPE var=(failure value) VERIFY(hObject) { (body of code) var = (success value) } return (var)} /* Method */</pre></tr></td></table> <br>The goal in a failure case is to return information that is reasonable. This way the code calling this method function never knows that the method function failed due to a bad memory pointer, which more than likely would have crashed your program anyway. <br><br>Consider how to create a fault-tolerant Randnext(). <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Fault-tolerant RandNext() method function</b>int APIENTRY RandNext( HRAND hRand ){ int nRand=0; VERIFY(hRand) { hRand->lRand = NEXTRAND(hRand->lRand); nRand = (int)FINALRAND(hRand->lRand); } return(nRand);} /* RandNext */</pre></tr></td></table> <br>In the case of the RandNext() method function, the failure case is to return zero. While zero is admittedly not random, at least the program using the random number generator is not going to bomb and you will be notified of the run-time object verification failure. <br><br><b>4.6.3 Summary</b> <br><br>In practice, I have found writing method functions following the state machine model to be a highly effective means of writing a fault-tolerant program. <br><br>You may be wondering if writing method functions that are fault-tolerant is even worth it. After all, if a method function fails, that means that an object handle is invalid. And if an object handle is invalid, won't the invalid handle just cause an onslaught of failures? <br><br>During development, yes, an onslaught of failures generally occurs, but what about when the program is in its final shipping form? It has been my experience that most faults in a released program cause only a few failures. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Most faults in a shipping product do not cause an onslaught of failures. </tr></td></table></blockquote>Isolating and recovering from these failures using run-time object verification allows the program to continue running.<br><a name="randomexample"><br></a><big><b>4.7 Random Number Generator Source Using Classes</b></big> <br><br>It is time to bring together everything that has been learned in this chapter and rewrite the random number generator. <br><br>The interface specification (NEWHANDLE() and function prototypes) remains the same and is contained earlier in this chapter. The final implementation of the random number generator source that uses the macros defined in this chapter is as follows. <br><br><table bgcolor="#CCCCEE" cellpadding=0 cellspacing=0><tr><td><pre><b>Random number generator implementation, final version</b>#include "app.h"USEWINASSERTCLASS(hRand, HRAND) { long lRand; };HRAND APIENTRY RandCreate( int nSeed ){ HRAND hRand; NEWOBJ(hRand); hRand->lRand = nSeed; return (hRand);} /* RandCreate */HRAND APIENTRY RandDestroy( HRAND hRand ){ VERIFYZ(hRand) { FREE(hRand); } return (NULL);} /* RandDestroy */int APIENTRY RandNext( HRAND hRand ){ int nRand=0; VERIFY(hRand) { hRand->lRand = NEXTRAND(hRand->lRand); nRand = (int)FINALRAND(hRand->lRand); } return(nRand);} /* RandNext */</pre></tr></td></table> <br>The CLASS() macro is used in the source file that implements the HRAND class object, not in an include file. <br><br>This random number generator implementation is contained in its own source file or module separate from all other modules. This ensures that the HRAND implementation is known only to the functions that implement the random number generator and is not known to functions that simply use random numbers. <br><br>The interface specification for this random number module is contained in app.h and is accessed through #include "app.h". The interface specification contains the NEWHANDLE(HRAND) declaration and prototypes for RandCreate(), RandDestroy() and RandNext(). <br><br>USEWINASSERT allows the code to use the WinAssert() macro, which is used by the VERIFY() and VERIFYZ() macros, and makes the current filename known through the szSRCFILE variable, which is used by the NEWOBJ() and WinAssert() macros. <br><br>The CLASS() macro allocates and initializes a class descriptor for HRAND and binds the HRAND handle to an actual data structure. The class descriptor is used by the NEWOBJ(), VERIFY() and VERIFYZ() macros. The binding of the HRAND handle to an actual data structure allows us to implement the random number generator, because indirections on hRand are now possible. <br><br>RandCreate() uses NEWOBJ() to create a new object, initializes it and returns the handle to the caller. <br><br>RandDestroy() performs run-time object verification on the hRand variable by using VERIFYZ(). If hRand is non-zero and valid, the object is freed by using FREE(). Finally, NULL is returned because all destroy methods return NULL by convention. <br><br>RandNext() performs run-time object verification on the hRand variable by using VERIFY(). If hRand is valid, a new random number is generated. Finally, the next random number (or an error random number of zero) is returned. <br><br>There is a lot going on behind the scenes in this code. The object-oriented macros are actually hiding a lot of code. It is instructive to see everything that is going on by running this source through the preprocessor pass of the compiler and viewing the resulting output. <br><br><table bgcolor="#F0F0F0"><tr><td><img src="images/windows.gif">In Microsoft C8, this is done with the /P command line option and results in an .i file. </td></tr></table><br><a name="cppcomparison"><br></a><big><b>4.8 A Comparison with C++ Data Hiding</b></big> <br><br>C++ does have a lot to offer (inheritance and virtual functions), but one of the things I do not like about C++ is that it does not allow for the complete data hidi
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -