📄 chapter4.html
字号:
<html><head><title>Writing Bug-Free C Code: The Class Methodology</title></head><body><center><font size="+3">Chapter 4: The Class Methodology</font><br><a href="index.html">Writing Bug-Free C Code</a><br></center><br><center><table><tr><td valign=top><small><a href="#traditional">4.1 The Problem with Traditional Techniques</a><br><a href="#newmodel">4.2 The New Object Model</a><br><a href="#compiletypecheck">4.3 Compile-Time Type Checking</a><br><a href="#runtypecheck">4.4 Run-Time Type Checking</a><br><a href="#managememory">4.5 Managing Memory</a><br><small></td><td width=30> </td><td valign=top><small><a href="#faulttolerant">4.6 Fault-Tolerant Methods</a><br><a href="#randomexample">4.7 Random Number Generator Source Using Classes</a><br><a href="#cppcomparison">4.8 A Comparison with C++ Data Hiding</a><br><a href="#summary">4.9 Chapter Summary</a><br></small></td></tr></table></center><br><br>The class methodology presented in this chapter is the core methodology presented in this book. It solves the data hiding problem. You will produce code that contains fewer bugs by using this methodology because it prevents and detects bugs. <br><br>The class methodology helps to prevent bugs by making it easier to write C code. It does this by eliminating data structures (class declarations) from include files, which makes a project easier to understand (because there is not as much global information), which makes it easier to write C code, which helps to eliminate bugs. This class methodology, which uses private class declarations, is different from C++, which uses public class declarations. <br><br>The class methodology helps detect bugs by providing for both compile-time and run-time type checking of pointers (handles) to class objects. This run-time type checking catches a lot of bugs for you since invalid object handles (the cause of a lot of bugs) are automatically detected and reported. <br><br>All the code introduced in this chapter can also be found in one convenient location in the <a href="appendix.html">Code Listings Appendix</a>.<br><a name="traditional"><br></a><big><b>4.1 The Problem with Traditional Techniques</b></big> <br><br><b>4.1.1 Data Structures Declared in Include Files</b> <br><br>The problem in a lot of projects is the number of data declarations in include files. Consider what happens when a small project grows gradually into a large project. In the small project, you have a small team of programmers who all know the project reasonably well. Each person is contributing code as well as data declarations. Some new data declarations undoubtedly refer to previous data declarations. This methodology works fine for small isolated projects. <br><br>What happens as the project grows and more programmers are added to the project? The old programmers continue to code as they always have. The new programmers have a steep learning curve. In order for them to become productive on the project, they first have a lot to learn about how it works. When the new programmer does come up to speed on the project, you now have one more programmer adding information to the pool of information that must now be learned by everyone. <br><br>It does not take too long before your pool of information is so large and interconnected that it becomes impossible for any one person to fully understand the project as a whole. At this point, the next logical step is to have individual programmers within the group specialize in a particular area of the code or project. This division of labor can be implemented successfully, but too often it just creates isolated groups with little communication and very little code sharing among groups. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> The problem with large development efforts is information overload in the form of data structure declarations that are found in include files. </tr></td></table></blockquote>The single biggest problem with any large project is that data structures are declared in include files. Any programming methodology that attempts to solve the information overload issue must also address data structures declared in include files. <br><br><b>4.1.2 Directly Accessing Data Structures</b> <br><br>Once data structure declarations are placed into include files, those declarations become public knowledge. <br><br>Do you consider this a good or a bad thing? What are the pros and cons? <br><br>The pros and cons of public data structures depend totally upon the size of the project. For small projects isolated to one programmer, public data structures can actually speed up the development time of the project. However, for any moderate to large project, with no matter how many programmers, public data structures quickly becomes a bad thing. <br><br>The primary problem with public data structures is that they promote writing code that directly accesses the data structure instead of calling a function that manipulates the data item. This direct access is bad because the distinction between the implementor of the data object and the user of the data object becomes totally blurred. <br><br>This blurring between the implementor and user of a data object over time leads to a project that is impossible to modify in any way. Just think what would happen if the data object needed to be changed to support a new feature. All code that directly accesses that data object would have to be changed. This is obviously very undesirable. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> Any programming methodology that attempts to solve the information overload issue must also address how data structures are to be accessed. </tr></td></table></blockquote><b>4.1.3 Compilation Times</b> <br><br>Another problem with public data structures is the time needed to compile source files whenever any change is made to a public structure. Any change to the structure may force you to recompile every source file that references the data structure. Determining this set of files is not always easy and to avoid possible problems you end up compiling the entire program. This actually works quite well for small projects, but what about large projects? What about large projects under version control software? Complete builds of a project may take anywhere from several minutes to several hours. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> A change to a single data structure should require, at most, only one source file to be recompiled. </tr></td></table></blockquote>The ideal situation that you should strive for, if at all possible, is a one-to-one dependency between data structure and source file. In other words, a change to a single data structure should require, at most, one source file to be recompiled.<br><a name="newmodel"><br></a><big><b>4.2 The New Object Model</b></big> <br><br>Any solution to the information overload problem must address two key issues involving data structure declarations: <br><br><i>Where are data structures placed?</i> <br><br><i>How are data structures accessed?</i> <br><br><b>4.2.1 Terminology</b> <br><br>Before continuing, some terms need to be defined. An object is any data declaration. A method is a function that acts upon an object. A handle is an abstract object identifier. An instance is a unique occurrence of an object that occupies space in memory. <br><br><b>4.2.2 Private Objects and Public Methods</b> <br><br>The traditional software development approach is one of public access to objects and public methods. To manipulate the object, you either directly access the object or you call one of the methods of the object. <br><br>The new object model I propose is one of private access to objects and public methods. The only way to manipulate an object is by calling one of the methods of the object. This implies that an object has a minimum of two method functions: one method function to create an instance of an object and another method function that destroys an instance. <br><br>Private access and public methods also imply that an object's data declaration and method functions are contained in the one source file that implements the object and not an include file. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> The object model must support private objects and public methods. </tr></td></table></blockquote>Private objects is just another term for complete data hiding. The data of an object is visible only to the implementor of the object and not to the user of the object. <br><br>If this object model can actually be implemented, it will solve the two key issues involving data structure declarations. Where are data structures placed? (in source files, not include files). How are data structures accessed? (privately -- only through method functions). <br><br>Let me relate the problem to the real-world problem of generating random numbers. The standard C library routines provide one global random number generator with functions or methods to seed and return the next random number. These functions are srand() and rand(). How would you extend this model to provide multiple independent random number generators? <br><br>One simple extension would be to add another argument to srand() and rand() that specifies which random number generator to use out of a static array of possible generators. While this design does work, it has several problems. All code that uses the new random number generator interface must cooperate on which indexes to use. What if the random number generator code is in a Windows DLL, which any number of applications can link to and use? Again, all these applications must cooperate on which indexes to use. The static array implementation also imposes a fixed maximum to the number of random number generators that can be used. Therefore, a static array is a bad implementation choice for a generalized object model. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> The object model must support an unlimited number of dynamically allocated objects. </tr></td></table></blockquote>What this means is that you need an object model that supports dynamically allocated memory. In terms of the random number generator, you need a data structure containing the data needed to implement the random number that is created and destroyed as needed. The method functions srand() and rand() then operate upon this dynamically allocated data structure. <br><br>Creating and destroying objects as needed is also much better than static arrays because all objects now share all available memory. This means that your program is not limited by some arbitrarily picked array bounds but instead by how much memory there is. <br><br><table bgcolor="#F0F0F0"><tr><td><img src="images/windows.gif"> <b>4.2.3 Windows Object Model</b> <br><br>This object model is starting to look like Windows. Think about CreateWindow(), a public method that creates a new window. You have no idea how Windows implements the window, which is a private object, and you are limited in how many windows you can create only by the amount of available (user heap) memory. <br><br>However, there are problems with the Windows object model. In SDKs prior to Windows 3.1, all objects had the same data type, namely HANDLE, which was defined to be a WORD. What this means is that if you passed an HPEN to a function that was expecting an HBRUSH, the compiler would certainly not complain. Worse yet, Windows might not even complain at run-time about the type mismatch. What if the function was SelectObject(), which allows any GDI object to be passed to it? Both HPEN and HBRUSH are GDI objects. SelectObject() would be selecting a pen and not a brush. Your program would not perform as expected until you had tracked down the cause of the wrong object being selected. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> The object model must support object handles that are type checkable by the compiler. </tr></td></table></blockquote>The Windows 3.1 SDK has fixed this problem by allowing you to #define STRICT before including windows.h. What this does is change the type of objects from WORD to a near pointer of a dummy public structure. This in turn allows the compiler to perform type checking. <br><br>However, what if you want to implement an object that one of the Windows handles points to? The problem is that the handle is already declared to point to a dummy public structure, but this structure is obviously not the true implementation. This is ugly and will not work for us, so another technique needs to be found.</td></tr></table><br><a name="compiletypecheck"><br></a><big><b>4.3 Compile-Time Type Checking</b></big> <br><br>We have a requirement for an object model in which there are private objects and public methods that act upon the objects. So how are handles to private objects going to be type checked? Remember, the object is private and the compiler is doing the type checking in modules that do not have access to the object's data declaration. <br><br>All objects are dynamic and every object type in the system has at least two method functions. One method creates an object, returning a handle to the object, and another method destroys the object. <br><br>A handle could be an index into an object table. It could be a near pointer into a private heap. It could be a long pointer to the object. It could also be a global memory handle in which the object is contained. The point is that the user of a handle does not know and does not need to know exactly what a handle is. To be consistent, however, all objects in an object system usually produce the same type of handle. This prevents some handles from being indexes and some from being memory pointers, which would only confuse the situation. <blockquote><table bgcolor="#E0E0E0" border=1 cellpadding=2 cellspacing=0><tr><td> The user of a handle does not know and does not need to know exactly what a handle is. </tr></td></table></blockquote><b>4.3.1 The Problem</b> <br><br>There are almost unlimited numbers of ways to implement this object system except for the requirement that object handles must be type checkable by the compiler. This obviously implies that a handle must be some data type, because only data types can be type checked by the compiler.
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -