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

Thinking in C plus plu (P10) pot

50 227 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 166,79 KB

Nội dung

430 Thinking in C++ www.BruceEckel.com } ///:~ The static char* s holds its value between calls of oneChar( ) because its storage is not part of the stack frame of the function, but is in the static storage area of the program. When you call oneChar( ) with a char* argument, s is assigned to that argument, and the first character of the array is returned. Each subsequent call to oneChar( ) without an argument produces the default value of zero for charArray , which indicates to the function that you are still extracting characters from the previously initialized value of s . The function will continue to produce characters until it reaches the null terminator of the character array, at which point it stops incrementing the pointer so it doesn’t overrun the end of the array. But what happens if you call oneChar( ) with no arguments and without previously initializing the value of s ? In the definition for s , you could have provided an initializer, static char* s = 0; but if you do not provide an initializer for a static variable of a built-in type, the compiler guarantees that variable will be initialized to zero (converted to the proper type) at program start- up. So in oneChar( ) , the first time the function is called, s is zero. In this case, the if(!s) conditional will catch it. The initialization above for s is very simple, but initialization for static objects (like all other objects) can be arbitrary expressions involving constants and previously declared variables and functions. You should be aware that the function above is very vulnerable to multithreading problems; whenever you design functions containing static variables you should keep multithreading issues in mind. 10: Name Control 431 static class objects inside functions The rules are the same for static objects of user-defined types, including the fact that some initialization is required for the object. However, assignment to zero has meaning only for built-in types; user-defined types must be initialized with constructor calls. Thus, if you don’t specify constructor arguments when you define the static object, the class must have a default constructor. For example, //: C10:StaticObjectsInFunctions.cpp #include <iostream> using namespace std; class X { int i; public: X(int ii = 0) : i(ii) {} // Default ~X() { cout << "X::~X()" << endl; } }; void f() { static X x1(47); static X x2; // Default constructor required } int main() { f(); } ///:~ The static objects of type X inside f( ) can be initialized either with the constructor argument list or with the default constructor. This construction occurs the first time control passes through the definition, and only the first time. Static object destructors Destructors for static objects (that is, all objects with static storage, not just local static objects as in the example above) are called when main( ) exits or when the Standard C library function exit( ) is explicitly called. In most implementations, main( ) just calls exit( ) when it terminates. This means that it can be dangerous to call exit( ) inside a destructor because you can end up with infinite 432 Thinking in C++ www.BruceEckel.com recursion. Static object destructors are not called if you exit the program using the Standard C library function abort( ) . You can specify actions to take place when leaving main( ) (or calling exit( ) ) by using the Standard C library function atexit( ) . In this case, the functions registered by atexit( ) may be called before the destructors for any objects constructed before leaving main( ) (or calling exit( ) ). Like ordinary destruction, destruction of static objects occurs in the reverse order of initialization. However, only objects that have been constructed are destroyed. Fortunately, the C++ development tools keep track of initialization order and the objects that have been constructed. Global objects are always constructed before main( ) is entered and destroyed as main( ) exits, but if a function containing a local static object is never called, the constructor for that object is never executed, so the destructor is also not executed. For example, //: C10:StaticDestructors.cpp // Static object destructors #include <fstream> using namespace std; ofstream out("statdest.out"); // Trace file class Obj { char c; // Identifier public: Obj(char cc) : c(cc) { out << "Obj::Obj() for " << c << endl; } ~Obj() { out << "Obj::~Obj() for " << c << endl; } }; Obj a('a'); // Global (static storage) // Constructor & destructor always called void f() { static Obj b('b'); } 10: Name Control 433 void g() { static Obj c('c'); } int main() { out << "inside main()" << endl; f(); // Calls static constructor for b // g() not called out << "leaving main()" << endl; } ///:~ In Obj , the char c acts as an identifier so the constructor and destructor can print out information about the object they’re working on. The Obj a is a global object, so the constructor is always called for it before main( ) is entered, but the constructors for the static Obj b inside f( ) and the static Obj c inside g( ) are called only if those functions are called. To demonstrate which constructors and destructors are called, only f( ) is called. The output of the program is Obj::Obj() for a inside main() Obj::Obj() for b leaving main() Obj::~Obj() for b Obj::~Obj() for a The constructor for a is called before main( ) is entered, and the constructor for b is called only because f( ) is called. When main( ) exits, the destructors for the objects that have been constructed are called in reverse order of their construction. This means that if g( ) is called, the order in which the destructors for b and c are called depends on whether f( ) or g( ) is called first. Notice that the trace file ofstream object out is also a static object – since it is defined outside of all functions, it lives in the static storage area. It is important that its definition (as opposed to an extern declaration) appear at the beginning of the file, before there 434 Thinking in C++ www.BruceEckel.com is any possible use of out . Otherwise, you’ll be using an object before it is properly initialized. In C++, the constructor for a global static object is called before main( ) is entered, so you now have a simple and portable way to execute code before entering main( ) and to execute code with the destructor after exiting main( ) . In C, this was always a trial that required you to root around in the compiler vendor’s assembly- language startup code. Controlling linkage Ordinarily, any name at file scope (that is, not nested inside a class or function) is visible throughout all translation units in a program. This is often called external linkage because at link time the name is visible to the linker everywhere, external to that translation unit. Global variables and ordinary functions have external linkage. There are times when you’d like to limit the visibility of a name. You might like to have a variable at file scope so all the functions in that file can use it, but you don’t want functions outside that file to see or access that variable, or to inadvertently cause name clashes with identifiers outside the file. An object or function name at file scope that is explicitly declared static is local to its translation unit (in the terms of this book, the cpp file where the declaration occurs). That name has internal linkage . This means that you can use the same name in other translation units without a name clash. One advantage to internal linkage is that the name can be placed in a header file without worrying that there will be a clash at link time. Names that are commonly placed in header files, such as const definitions and inline functions, default to internal linkage. (However, const defaults to internal linkage only in C++; in C it defaults to external linkage.) Note that linkage refers only to 10: Name Control 435 elements that have addresses at link/load time; thus, class declarations and local variables have no linkage. Confusion Here’s an example of how the two meanings of static can cross over each other. All global objects implicitly have static storage class, so if you say (at file scope), int a = 0; then storage for a will be in the program’s static data area, and the initialization for a will occur once, before main( ) is entered. In addition, the visibility of a is global across all translation units. In terms of visibility, the opposite of static (visible only in this translation unit) is extern , which explicitly states that the visibility of the name is across all translation units. So the definition above is equivalent to saying extern int a = 0; But if you say instead, static int a = 0; all you’ve done is change the visibility, so a has internal linkage. The storage class is unchanged – the object resides in the static data area whether the visibility is static or extern . Once you get into local variables, static stops altering the visibility and instead alters the storage class. If you declare what appears to be a local variable as extern , it means that the storage exists elsewhere (so the variable is actually global to the function). For example: //: C10:LocalExtern.cpp //{L} LocalExtern2 #include <iostream> int main() { 436 Thinking in C++ www.BruceEckel.com extern int i; std::cout << i; } ///:~ //: C10:LocalExtern2.cpp {O} int i = 5; ///:~ With function names (for non-member functions), static and extern can only alter visibility, so if you say extern void f(); it’s the same as the unadorned declaration void f(); and if you say, static void f(); it means f( ) is visible only within this translation unit – this is sometimes called file static . Other storage class specifiers You will see static and extern used commonly. There are two other storage class specifiers that occur less often. The auto specifier is almost never used because it tells the compiler that this is a local variable. auto is short for “automatic” and it refers to the way the compiler automatically allocates storage for the variable. The compiler can always determine this fact from the context in which the variable is defined, so auto is redundant. A register variable is a local ( auto ) variable, along with a hint to the compiler that this particular variable will be heavily used so the compiler ought to keep it in a register if it can. Thus, it is an optimization aid. Various compilers respond differently to this hint; they have the option to ignore it. If you take the address of the variable, the register specifier will almost certainly be ignored. You 10: Name Control 437 should avoid using register because the compiler can usually do a better job of optimization than you. Namespaces Although names can be nested inside classes, the names of global functions, global variables, and classes are still in a single global name space. The static keyword gives you some control over this by allowing you to give variables and functions internal linkage (that is, to make them file static). But in a large project, lack of control over the global name space can cause problems. To solve these problems for classes, vendors often create long complicated names that are unlikely to clash, but then you’re stuck typing those names. (A typedef is often used to simplify this.) It’s not an elegant, language-supported solution. You can subdivide the global name space into more manageable pieces using the namespace feature of C++. The namespace keyword, similar to class , struct , enum , and union , puts the names of its members in a distinct space. While the other keywords have additional purposes, the creation of a new name space is the only purpose for namespace . Creating a namespace The creation of a namespace is notably similar to the creation of a class : //: C10:MyLib.cpp namespace MyLib { // Declarations } int main() {} ///:~ This produces a new namespace containing the enclosed declarations. There are significant differences from class , struct , union and enum , however: 438 Thinking in C++ www.BruceEckel.com • A namespace definition can appear only at global scope, or nested within another namespace. • No terminating semicolon is necessary after the closing brace of a namespace definition. • A namespace definition can be “continued” over multiple header files using a syntax that, for a class, would appear to be a redefinition: //: C10:Header1.h #ifndef HEADER1_H #define HEADER1_H namespace MyLib { extern int x; void f(); // } #endif // HEADER1_H ///:~ //: C10:Header2.h #ifndef HEADER2_H #define HEADER2_H #include "Header1.h" // Add more names to MyLib namespace MyLib { // NOT a redefinition! extern int y; void g(); // } #endif // HEADER2_H ///:~ //: C10:Continuation.cpp #include "Header2.h" int main() {} ///:~ • A namespace name can be aliased to another name, so you don’t have to type an unwieldy name created by a library vendor: //: C10:BobsSuperDuperLibrary.cpp namespace BobsSuperDuperLibrary { class Widget { /* */ }; 10: Name Control 439 class Poppit { /* */ }; // } // Too much to type! I’ll alias it: namespace Bob = BobsSuperDuperLibrary; int main() {} ///:~ • You cannot create an instance of a namespace as you can with a class. Unnamed namespaces Each translation unit contains an unnamed namespace that you can add to by saying “ namespace ” without an identifier: //: C10:UnnamedNamespaces.cpp namespace { class Arm { /* */ }; class Leg { /* */ }; class Head { /* */ }; class Robot { Arm arm[4]; Leg leg[16]; Head head[3]; // } xanthan; int i, j, k; } int main() {} ///:~ The names in this space are automatically available in that translation unit without qualification. It is guaranteed that an unnamed space is unique for each translation unit. If you put local names in an unnamed namespace, you don’t need to give them internal linkage by making them static . C++ deprecates the use of file statics in favor of the unnamed namespace. Friends You can inject a friend declaration into a namespace by declaring it within an enclosed class: [...]... static constvariable that allows you to define a constant value inside a class body It’s also possible 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 = 100; 448 Thinking in C+ + www.BruceEckel.com static const... members inside local classes (a local class is a class defined inside a function) Thus, //: C1 0:Local.cpp // Static members & local classes #include using namespace std; // Nested class CAN have static data members: class Outer { class Inner { static int i; // OK }; }; int Outer::Inner::i = 47; // Local class cannot have static data members: void f() { class Local { public: //! static int i;... Suppose you have a second namespace that contains some of the names in namespace Math : //: C1 0:NamespaceOverriding2.h #ifndef NAMESPACEOVERRIDING2_H #define NAMESPACEOVERRIDING2_H #include "NamespaceInt.h" namespace Calculation { using namespace Int; Integer divide(Integer, Integer); // } #endif // NAMESPACEOVERRIDING2_H ///:~ Since this namespace is also introduced with a using directive, you have the... directive: //: C1 0:UsingDeclaration.h #ifndef USINGDECLARATION_H #define USINGDECLARATION_H namespace U { inline void f() {} inline void g() {} } namespace V { inline void f() {} inline void g() {} } #endif // USINGDECLARATION_H ///:~ //: C1 0:UsingDeclaration1.cpp #include "UsingDeclaration.h" void h() { using namespace U; // Using directive using V::f; // Using declaration f(); // Calls V::f(); U::f();... #include using namespace std; 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() . namespace. Friends You can inject a friend declaration into a namespace by declaring it within an enclosed class: 440 Thinking in C+ + www.BruceEckel.com //: C1 0:FriendInjection.cpp namespace. producing an ambiguity. 444 Thinking in C+ + www.BruceEckel.com The using declaration You can inject names one at a time into the current scope with a using declaration . Unlike the using . is actually global to the function). For example: //: C1 0:LocalExtern.cpp //{L} LocalExtern2 #include <iostream> int main() { 436 Thinking in C+ + www.BruceEckel.com extern int

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