1. Trang chủ
  2. » Công Nghệ Thông Tin

Thinking in C plus plu (P9) ppt

50 242 0

Đang tải... (xem toàn văn)

Tài liệu hạn chế xem trước, để xem đầy đủ mời bạn chọn Tải xuống

THÔNG TIN TÀI LIỆU

Thông tin cơ bản

Định dạng
Số trang 50
Dung lượng 163,55 KB

Nội dung

380 Thinking in C++ www.BruceEckel.com int main() { cout << "sizeof(Bunch) = " << sizeof(Bunch) << ", sizeof(i[1000]) = " << sizeof(int[1000]) << endl; } ///:~ The use of enum here is guaranteed to occupy no storage in the object, and the enumerators are all evaluated at compile time. You can also explicitly establish the values of the enumerators: enum { one = 1, two = 2, three }; With integral enum types, the compiler will continue counting from the last value, so the enumerator three will get the value 3. In the StringStack.cpp example above, the line: static const int size = 100; would be instead: enum { size = 100 }; Although you’ll often see the enum technique in legacy code, the static const feature was added to the language to solve just this problem. However, there is no overwhelming reason that you must choose static const over the enum hack, and in this book the enum hack is used because it is supported by more compilers at the time this book was written. const objects & member functions Class member functions can be made const . What does this mean? To understand, you must first grasp the concept of const objects. A const object is defined the same for a user-defined type as a built- in type. For example: const int i = 1; const blob b(2); 8: Constants 381 Here, b is a const object of type blob . Its constructor is called with an argument of two. For the compiler to enforce const ness, it must ensure that no data members of the object are changed during the object’s lifetime. It can easily ensure that no public data is modified, but how is it to know which member functions will change the data and which ones are “safe” for a const object? If you declare a member function const , you tell the compiler the function can be called for a const object. A member function that is not specifically declared const is treated as one that will modify data members in an object, and the compiler will not allow you to call it for a const object. It doesn’t stop there, however. Just claiming a member function is const doesn’t guarantee it will act that way, so the compiler forces you to reiterate the const specification when defining the function. (The const becomes part of the function signature, so both the compiler and linker check for const ness.) Then it enforces const ness during the function definition by issuing an error message if you try to change any members of the object or call a non- const member function. Thus, any member function you declare const is guaranteed to behave that way in the definition. To understand the syntax for declaring const member functions, first notice that preceding the function declaration with const means the return value is const , so that doesn’t produce the desired results. Instead, you must place the const specifier after the argument list. For example, //: C08:ConstMember.cpp class X { int i; public: X(int ii); int f() const; }; X::X(int ii) : i(ii) {} int X::f() const { return i; } 382 Thinking in C++ www.BruceEckel.com int main() { X x1(10); const X x2(20); x1.f(); x2.f(); } ///:~ Note that the const keyword must be repeated in the definition or the compiler sees it as a different function. Since f( ) is a const member function, if it attempts to change i in any way or to call another member function that is not const , the compiler flags it as an error. You can see that a const member function is safe to call with both const and non- const objects. Thus, you could think of it as the most general form of a member function (and because of this, it is unfortunate that member functions do not automatically default to const ). Any function that doesn’t modify member data should be declared as const , so it can be used with const objects. Here’s an example that contrasts a const and non- const member function: //: C08:Quoter.cpp // Random quote selection #include <iostream> #include <cstdlib> // Random number generator #include <ctime> // To seed random generator using namespace std; class Quoter { int lastquote; public: Quoter(); int lastQuote() const; const char* quote(); }; Quoter::Quoter(){ lastquote = -1; srand(time(0)); // Seed random number generator 8: Constants 383 } int Quoter::lastQuote() const { return lastquote; } const char* Quoter::quote() { static const char* quotes[] = { "Are we having fun yet?", "Doctors always know best", "Is it Atomic?", "Fear is obscene", "There is no scientific evidence " "to support the idea " "that life is serious", "Things that make us happy, make us wise", }; const int qsize = sizeof quotes/sizeof *quotes; int qnum = rand() % qsize; while(lastquote >= 0 && qnum == lastquote) qnum = rand() % qsize; return quotes[lastquote = qnum]; } int main() { Quoter q; const Quoter cq; cq.lastQuote(); // OK //! cq.quote(); // Not OK; non const function for(int i = 0; i < 20; i++) cout << q.quote() << endl; } ///:~ Neither constructors nor destructors can be const member functions because they virtually always perform some modification on the object during initialization and cleanup. The quote( ) member function also cannot be const because it modifies the data member lastquote (see the return statement). However, lastQuote( ) makes no modifications, and so it can be const and can be safely called for the const object cq . 384 Thinking in C++ www.BruceEckel.com mutable: bitwise vs. logical const What if you want to create a const member function, but you’d still like to change some of the data in the object? This is sometimes referred to as the difference between bitwise const and logical const (also sometimes called memberwise const ) . Bitwise const means that every bit in the object is permanent, so a bit image of the object will never change. Logical const means that, although the entire object is conceptually constant, there may be changes on a member-by- member basis. However, if the compiler is told that an object is const , it will jealously guard that object to ensure bitwise const ness. To effect logical const ness, there are two ways to change a data member from within a const member function. The first approach is the historical one and is called casting away constness . It is performed in a rather odd fashion. You take this (the keyword that produces the address of the current object) and cast it to a pointer to an object of the current type. It would seem that this is already such a pointer. However, inside a const member function it’s actually a const pointer, so by casting it to an ordinary pointer, you remove the const ness for that operation. Here’s an example: //: C08:Castaway.cpp // "Casting away" constness class Y { int i; public: Y(); void f() const; }; Y::Y() { i = 0; } void Y::f() const { //! i++; // Error const member function ((Y*)this)->i++; // OK: cast away const-ness // Better: use C++ explicit cast syntax: (const_cast<Y*>(this))->i++; } 8: Constants 385 int main() { const Y yy; yy.f(); // Actually changes it! } ///:~ This approach works and you’ll see it used in legacy code, but it is not the preferred technique. The problem is that this lack of const ness is hidden away in a member function definition, and you have no clue from the class interface that the data of the object is actually being modified unless you have access to the source code (and you must suspect that const ness is being cast away, and look for the cast). To put everything out in the open, you should use the mutable keyword in the class declaration to specify that a particular data member may be changed inside a const object: //: C08:Mutable.cpp // The "mutable" keyword class Z { int i; mutable int j; public: Z(); void f() const; }; Z::Z() : i(0), j(0) {} void Z::f() const { //! i++; // Error const member function j++; // OK: mutable } int main() { const Z zz; zz.f(); // Actually changes it! } ///:~ This way, the user of the class can see from the declaration which members are likely to be modified in a const member function. 386 Thinking in C++ www.BruceEckel.com ROMability If an object is defined as const , it is a candidate to be placed in read- only memory (ROM), which is often an important consideration in embedded systems programming. Simply making an object const , however, is not enough – the requirements for ROMability are much stricter. Of course, the object must be bitwise- const , rather than logical- const . This is easy to see if logical const ness is implemented only through the mutable keyword, but probably not detectable by the compiler if const ness is cast away inside a const member function. In addition, 1. The class or struct must have no user-defined constructors or destructor. 2. There can be no base classes (covered in Chapter 14) or member objects with user-defined constructors or destructors. The effect of a write operation on any part of a const object of a ROMable type is undefined. Although a suitably formed object may be placed in ROM, no objects are ever required to be placed in ROM. volatile The syntax of volatile is identical to that for const , but volatile means “This data may change outside the knowledge of the compiler.” Somehow, the environment is changing the data (possibly through multitasking, multithreading or interrupts), and volatile tells the compiler not to make any assumptions about that data, especially during optimization. If the compiler says, “I read this data into a register earlier, and I haven’t touched that register,” normally it wouldn’t need to read the data again. But if the data is volatile , the compiler cannot make such an assumption because the data may have been changed by another process, and it must reread that data rather than 8: Constants 387 optimizing the code to remove what would normally be a redundant read. You create volatile objects using the same syntax that you use to create const objects. You can also create const volatile objects, which can’t be changed by the client programmer but instead change through some outside agency. Here is an example that might represent a class associated with some piece of communication hardware: //: C08:Volatile.cpp // The volatile keyword class Comm { const volatile unsigned char byte; volatile unsigned char flag; enum { bufsize = 100 }; unsigned char buf[bufsize]; int index; public: Comm(); void isr() volatile; char read(int index) const; }; Comm::Comm() : index(0), byte(0), flag(0) {} // Only a demo; won't actually work // as an interrupt service routine: void Comm::isr() volatile { flag = 0; buf[index++] = byte; // Wrap to beginning of buffer: if(index >= bufsize) index = 0; } char Comm::read(int index) const { if(index < 0 || index >= bufsize) return 0; return buf[index]; } int main() { 388 Thinking in C++ www.BruceEckel.com volatile Comm Port; Port.isr(); // OK //! Port.read(0); // Error, read() not volatile } ///:~ As with const , you can use volatile for data members, member functions, and objects themselves. You can only call volatile member functions for volatile objects. The reason that isr( ) can’t actually be used as an interrupt service routine is that in a member function, the address of the current object ( this ) must be secretly passed, and an ISR generally wants no arguments at all. To solve this problem, you can make isr( ) a static member function, a subject covered in Chapter 10. The syntax of volatile is identical to const , so discussions of the two are often treated together. The two are referred to in combination as the c-v qualifier . Summary The const keyword gives you the ability to define objects, function arguments, return values and member functions as constants, and to eliminate the preprocessor for value substitution without losing any preprocessor benefits. All this provides a significant additional form of type checking and safety in your programming. The use of so-called const correctness (the use of const anywhere you possibly can) can be a lifesaver for projects. Although you can ignore const and continue to use old C coding practices, it’s there to help you. Chapters 11 and on begin using references heavily, and there you’ll see even more about how critical it is to use const with function arguments. 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. 8: Constants 389 1. Create three const int values, then add them together to produce a value that determines the size of an array in an array definition. Try to compile the same code in C and see what happens (you can generally force your C++ compiler to run as a C compiler by using a command-line flag). 2. Prove to yourself that the C and C++ compilers really do treat constants differently. Create a global const and use it in a global constant expression; then compile it under both C and C++. 3. Create example const definitions for all the built-in types and their variants. Use these in expressions with other const s to make new const definitions. Make sure they compile successfully. 4. Create a const definition in a header file, include that header file in two .cpp files, then compile those files and link them. You should not get any errors. Now try the same experiment with C. 5. Create a const whose value is determined at runtime by reading the time when the program starts (you’ll have to use the <ctime> standard header). Later in the program, try to read a second value of the time into your const and see what happens. 6. Create a const array of char , then try to change one of the char s. 7. Create an extern const declaration in one file, and put a main( ) in that file that prints the value of the extern const . Provide an extern const definition in a second file, then compile and link the two files together. 8. Write two pointers to const long using both forms of the declaration. Point one of them to an array of long . Demonstrate that you can increment or decrement the pointer, but you can’t change what it points to. 9. Write a const pointer to a double , and point it at an array of double . Show that you can change what the pointer [...]... define inside a class definition is automatically an inline For example: //: C0 9:Inline.cpp // Inlines inside classes #include #include using namespace std; class Point { 400 Thinking in C+ + www.BruceEckel.com int i, j, k; public: Point(): i(0), j(0), k(0) {} Point(int ii, int jj, int kk) : i(ii), j(jj), k(kk) {} void print(const string& msg = "") const { if(msg.size() != 0) cout... next; } 408 Thinking in C+ + www.BruceEckel.com }; #endif // STASH4_H ///:~ The small functions obviously work well as inlines, but notice that the two largest functions are still left as non-inlines, since inlining them probably wouldn’t cause any performance gains: //: C0 9:Stash4.cpp {O} #include "Stash4.h" #include #include using namespace std; const int increment = 100; int Stash::add(void*... course, accessors and mutators don’t have to be simple pipelines to an internal variable Sometimes they can perform more sophisticated calculations The following example uses the Standard C library time functions to produce a simple Time class: 404 Thinking in C+ + www.BruceEckel.com //: C0 9:Cpptime.h // A simple time class #ifndef CPPTIME_H #define CPPTIME_H #include #include class Time... a class called MyString which contains a string and has a constructor that initializes the string, and a print( ) function Modify StringStack.cppso that the container holds MyString objects, and main( ) so it prints them Create a class containing a const member that you initialize in the constructor initializer list and an untagged enumeration that you use to determine an array size In ConstMember.cpp... (notice that there’s no Stack4.cpp ): //: C0 9:Stack4Test.cpp //{T} Stack4Test.cpp #include "Stack4.h" #include " /require.h" #include #include #include using namespace std; int main(int argc, char* argv[]) { requireArgs(argc, 1); // File name is argument ifstream in( argv[1]); assure (in, argv[1]); Stack textlines; string line; // Read file and store lines in the stack: while(getline (in, ... you to read or change 9: Inline Functions 401 part of the state of an object – that is, an internal variable or variables The reason inlines are so important for access functions can be seen in the following example: //: C0 9:Access.cpp // Inline access functions class Access { int i; public: int read() const { return i; } void set(int ii) { i = ii; } }; int main() { Access A; A.set(100); int x = A.read();... the function body for the function call, thus eliminating the overhead The inline code does occupy space, but if the function is small, this can actually take less space than the code generated to do an ordinary function call (pushing arguments on the stack and doing the CALL) An inline function in a header file has a special status, since you must include the header file containing the function and... functions Any function defined within a class body is automatically inline, but you can also make a non-class function inline by preceding it with the inline keyword However, for it to have any effect, you must include the function body with the declaration, otherwise the compiler will treat it as an ordinary function declaration Thus, inline int plusOne(int x); has no effect at all other than declaring... made noninline If you make other functions non-inline, at least keep updateLocal( )inline so that its code will be duplicated in the noninline functions, eliminating extra function-call overhead Here’s a small test program: //: C0 9:Cpptime.cpp // Testing a simple time class #include "Cpptime.h" #include using namespace std; int main() { Time start; for(int i = 1; i < 1000; i++) { cout . C0 8:ConstMember.cpp class X { int i; public: X(int ii); int f() const; }; X::X(int ii) : i(ii) {} int X::f() const { return i; } 382 Thinking in C+ + www.BruceEckel.com int main() { . never use macros, only inline functions. Any function defined within a class body is automatically inline, but you can also make a non-class function inline by preceding it with the inline keyword the macro 396 Thinking in C+ + www.BruceEckel.com definition. (This is a good practice to use when creating preprocessor macros.) Thus, #define FLOOR(x,b) ((x)>=(b)?0:1) Discovering the

Ngày đăng: 05/07/2014, 19:20