Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống
1
/ 88 trang
THÔNG TIN TÀI LIỆU
Thông tin cơ bản
Định dạng
Số trang
88
Dung lượng
297,23 KB
Nội dung
420 Thinking in C++ www.BruceEckel.com Improved error checking The require.h functions have been used up to this point without defining them (although assert( ) has also been used to help detect programmer errors where it’s appropriate). Now it’s time to define this header file. Inline functions are convenient here because they allow everything to be placed in a header file, which simplifies the process of using the package. You just include the header file and you don’t need to worry about linking an implementation file. You should note that exceptions (presented in detail in Volume 2 of this book) provide a much more effective way of handling many kinds of errors – especially those that you’d like to recover from – instead of just halting the program. The conditions that require.h handles, however, are ones which prevent the continuation of the program, such as if the user doesn’t provide enough command-line arguments or if a file cannot be opened. Thus, it’s acceptable that they call the Standard C Library function exit( ) . The following header file is placed in the book’s root directory so it’s easily accessed from all chapters. //: :require.h // Test for error conditions in programs // Local "using namespace std" for old compilers #ifndef REQUIRE_H #define REQUIRE_H #include <cstdio> #include <cstdlib> #include <fstream> #include <string> inline void require(bool requirement, const std::string& msg = "Requirement failed"){ using namespace std; if (!requirement) { fputs(msg.c_str(), stderr); fputs("\n", stderr); exit(1); } 9: Inline Functions 421 } inline void requireArgs(int argc, int args, const std::string& msg = "Must use %d arguments") { using namespace std; if (argc != args + 1) { fprintf(stderr, msg.c_str(), args); fputs("\n", stderr); exit(1); } } inline void requireMinArgs(int argc, int minArgs, const std::string& msg = "Must use at least %d arguments") { using namespace std; if(argc < minArgs + 1) { fprintf(stderr, msg.c_str(), minArgs); fputs("\n", stderr); exit(1); } } inline void assure(std::ifstream& in, const std::string& filename = "") { using namespace std; if(!in) { fprintf(stderr, "Could not open file %s\n", filename.c_str()); exit(1); } } inline void assure(std::ofstream& out, const std::string& filename = "") { using namespace std; if(!out) { fprintf(stderr, "Could not open file %s\n", filename.c_str()); exit(1); } } #endif // REQUIRE_H ///:~ 422 Thinking in C++ www.BruceEckel.com The default values provide reasonable messages that can be changed if necessary. You’ll notice that instead of using char* arguments, const string& arguments are used. This allows both char* and string s as arguments to these functions, and thus is more generally useful (you may want to follow this form in your own coding). In the definitions for requireArgs( ) and requireMinArgs( ) , one is added to the number of arguments you need on the command line because argc always includes the name of the program being executed as argument zero, and so always has a value that is one more than the number of actual arguments on the command line. Note the use of local “ using namespace std ” declarations within each function. This is because some compilers at the time of this writing incorrectly did not include the C standard library functions in namespace std , so explicit qualification would cause a compile- time error. The local declaration allows require.h to work with both correct and incorrect libraries without opening up the namespace std for anyone who includes this header file. Here’s a simple program to test require.h : //: C09:ErrTest.cpp //{T} ErrTest.cpp // Testing require.h #include " /require.h" #include <fstream> using namespace std; int main(int argc, char* argv[]) { int i = 1; require(i, "value must be nonzero"); requireArgs(argc, 1); requireMinArgs(argc, 1); ifstream in(argv[1]); assure(in, argv[1]); // Use the file name ifstream nofile("nofile.xxx"); // Fails: 9: Inline Functions 423 //! assure(nofile); // The default argument ofstream out("tmp.txt"); assure(out); } ///:~ You might be tempted to go one step further for opening files and add a macro to require.h : #define IFOPEN(VAR, NAME) \ ifstream VAR(NAME); \ assure(VAR, NAME); Which could then be used like this: IFOPEN(in, argv[1]) At first, this might seem appealing since it means there’s less to type. It’s not terribly unsafe, but it’s a road best avoided. Note that, once again, a macro looks like a function but behaves differently; it’s actually creating an object ( in ) whose scope persists beyond the macro. You may understand this, but for new programmers and code maintainers it’s just one more thing they have to puzzle out. C++ is complicated enough without adding to the confusion, so try to talk yourself out of using preprocessor macros whenever you can. Summary It’s critical that you be able to hide the underlying implementation of a class because you may want to change that implementation sometime later. You’ll make these changes for efficiency, or because you get a better understanding of the problem, or because some new class becomes available that you want to use in the implementation. Anything that jeopardizes the privacy of the underlying implementation reduces the flexibility of the language. Thus, the inline function is very important because it virtually eliminates the need for preprocessor macros and their attendant problems. With inlines, member functions can be as efficient as preprocessor macros. 424 Thinking in C++ www.BruceEckel.com The inline function can be overused in class definitions, of course. The programmer is tempted to do so because it’s easier, so it will happen. However, it’s not that big of an issue because later, when looking for size reductions, you can always change the functions to non-inlines with no effect on their functionality. The development guideline should be “First make it work, then optimize it.” Exercises Solutions to selected exercises can be found in the electronic document The Thinking in C++ Annotated Solution Guide , available for a small fee from www.BruceEckel.com. 1. Write a program that uses the F( ) macro shown at the beginning of the chapter and demonstrates that it does not expand properly, as described in the text. Repair the macro and show that it works correctly. 2. Write a program that uses the FLOOR( ) macro shown at the beginning of the chapter. Show the conditions under which it does not work properly. 3. Modify MacroSideEffects.cpp so that BAND( ) works properly. 4. Create two identical functions, f1( ) and f2( ) . Inline f1( ) and leave f2( ) as an non-inline function. Use the Standard C Library function clock( ) that is found in <ctime> to mark the starting point and ending points and compare the two functions to see which one is faster. You may need to make repeated calls to the functions inside your timing loop in order to get useful numbers. 5. Experiment with the size and complexity of the code inside the functions in Exercise 4 to see if you can find a break-even point where the inline function and the non- inline function take the same amount of time. If you have them available, try this with different compilers and note the differences. 6. Prove that inline functions default to internal linkage. 9: Inline Functions 425 7. Create a class that contains an array of char . Add an inline constructor that uses the Standard C library function memset( ) to initialize the array to the constructor argument (default this to ‘ ’), and an inline member function called print( ) to print out all the characters in the array. 8. Take the NestFriend.cpp example from Chapter 5 and replace all the member functions with inlines. Make them non- in situ inline functions. Also change the initialize( ) functions to constructors. 9. Modify StringStack.cpp from Chapter 8 to use inline functions. 10. Create an enum called Hue containing red, blue , and yellow . Now create a class called Color containing a data member of type Hue and a constructor that sets the Hue from its argument. Add access functions to “get” and “set” the Hue . Make all of the functions inlines. 11. Modify Exercise 10 to use the “accessor” and “mutator” approach. 12. Modify Cpptime.cpp so that it measures the time from the time that the program begins running to the time when the user presses the “Enter” or “Return” key. 13. Create a class with two inline member functions, such that the first function that’s defined in the class calls the second function, without the need for a forward declaration. Write a main that creates an object of the class and calls the first function. 14. Create a class A with an inline default constructor that announces itself. Now make a new class B and put an object of A as a member of B , and give B an inline constructor. Create an array of B objects and see what happens. 15. Create a large quantity of the objects from the previous Exercise, and use the Time class to time the difference 426 Thinking in C++ www.BruceEckel.com between non-inline constructors and inline constructors. (If you have a profiler, also try using that.) 16. Write a program that takes a string as the command-line argument. Write a for loop that removes one character from the string with each pass, and use the DEBUG( ) macro from this chapter to print the string each time. 17. Correct the TRACE( ) macro as specified in this chapter, and prove that it works correctly. 18. Modify the FIELD( ) macro so that it also contains an index number. Create a class whose members are composed of calls to the FIELD( ) macro. Add a member function that allows you to look up a field using its index number. Write a main( ) to test the class. 19. Modify the FIELD( ) macro so that it automatically generates access functions for each field (the data should still be private, however). Create a class whose members are composed of calls to the FIELD( ) macro. Write a main( ) to test the class. 20. Write a program that takes two command-line arguments: the first is an int and the second is a file name. Use require.h to ensure that you have the right number of arguments, that the int is between 5 and 10, and that the file can successfully be opened. 21. Write a program that uses the IFOPEN( ) macro to open a file as an input stream. Note the creation of the ifstream object and its scope. 22. (Challenging) Determine how to get your compiler to generate assembly code. Create a file containing a very small function and a main( ) that calls the function. Generate assembly code when the function is inlined and not inlined, and demonstrate that the inlined version does not have the function call overhead. 427 10: Name Control Creating names is a fundamental activity in programming, and when a project gets large, the number of names can easily be overwhelming. 428 Thinking in C++ www.BruceEckel.com C++ allows you a great deal of control over the creation and visibility of names, where storage for those names is placed, and linkage for names. The static keyword was overloaded in C before people knew what the term “overload” meant, and C++ has added yet another meaning. The underlying concept with all uses of static seems to be “something that holds its position” (like static electricity), whether that means a physical location in memory or visibility within a file. In this chapter, you’ll learn how static controls storage and visibility, and an improved way to control access to names via C++’s namespace feature. You’ll also find out how to use functions that were written and compiled in C. Static elements from C In both C and C++ the keyword static has two basic meanings, which unfortunately often step on each other’s toes: 1. Allocated once at a fixed address; that is, the object is created in a special static data area rather than on the stack each time a function is called. This is the concept of static storage . 2. Local to a particular translation unit (and local to a class scope in C++, as you will see later). Here, static controls the visibility of a name, so that name cannot be seen outside the translation unit or class. This also describes the concept of linkage , which determines what names the linker will see. This section will look at the above meanings of static as they were inherited from C. static variables inside functions When you create a local variable inside a function, the compiler allocates storage for that variable each time the function is called by 10: Name Control 429 moving the stack pointer down an appropriate amount. If there is an initializer for the variable, the initialization is performed each time that sequence point is passed. Sometimes, however, you want to retain a value between function calls. You could accomplish this by making a global variable, but then that variable would not be under the sole control of the function. C and C++ allow you to create a static object inside a function; the storage for this object is not on the stack but instead in the program’s static data area. This object is initialized only once, the first time the function is called, and then retains its value between function invocations. For example, the following function returns the next character in the array each time the function is called: //: C10:StaticVariablesInfunctions.cpp #include " /require.h" #include <iostream> using namespace std; char oneChar(const char* charArray = 0) { static const char* s; if(charArray) { s = charArray; return *s; } else require(s, "un-initialized s"); if(*s == '\0') return 0; return *s++; } char* a = "abcdefghijklmnopqrstuvwxyz"; int main() { // oneChar(); // require() fails oneChar(a); // Initializes s to a char c; while((c = oneChar()) != 0) cout << c << endl; [...]... to create arrays of static objects, both const and non-const The syntax is reasonably consistent: //: C1 0:StaticArray.cpp // Initializing static arrays in classes class Values { // static consts are initialized in- place: static const int scSize = 10 0; 448 Thinking in C+ + www.BruceEckel.com static const long scLong = 10 0; // Automatic counting works with static arrays // Arrays, Non-integral and non-const... non-const statics // must be initialized externally: static const int scInts[]; static const long scLongs[]; static const float scTable[]; static const char scLetters[]; static int size; static const float scFloat; static float table[]; static char letters[]; }; int Values::size = 10 0; const float Values::scFloat = 1. 1; const int Values::scInts[] = { 99, 47, 33, 11 , 7 }; const long Values::scLongs[] =... class Egg { static Egg e; int i; Egg(int ii) : i(ii) {} Egg(const Egg&); // Prevent copy-construction public: static Egg* instance() { return &e; } int val() const { return i; } }; Egg Egg::e(47); int main() { //! Egg x (1) ; // Error can't create an Egg // You can access the single instance: cout val() . Time class to time the difference 4 26 Thinking in C+ + www.BruceEckel.com between non-inline constructors and inline constructors. (If you have a profiler, also try using that.) 16 . Write. is actually global to the function). For example: //: C1 0:LocalExtern.cpp //{L} LocalExtern2 #include <iostream> int main() { 4 36 Thinking in C+ + www.BruceEckel.com extern int. character in the array each time the function is called: //: C1 0:StaticVariablesInfunctions.cpp #include " /require.h" #include <iostream> using namespace std; char oneChar(const