Issue #011 May, 1996 Contents: The Meaning of "Static" Local Statics and Constructors/Destructors Introduction to Stream I/O Part 6 - Seeking in Files Using C++ as a Better C Part 11 - Jumping Past Initialization Introduction to Templates Part 3 - Template Arguments THE MEANING OF "STATIC" Someone asked about the meaning of the term "static" in C++. This particular term is perhaps the most overworked one in the language. It's both a descriptive word and a C++ keyword that is used in various ways. "static" as a descriptive term refers to the lifetime of C++ memory or storage locations. There are several types of storage: - static - dynamic (heap) - auto (stack) A typical storage layout scheme will have the following arrangement, from lowest to highest virtual memory address: text (program code) static (initialized and uninitialized data) heap (large virtual address space gap) stack with the heap and stack growing toward each other. The C++ draft standard does not mandate this arrangement, and this example is only an illustration of one way of doing it. Static storage thus refers to memory locations that persist for the life of the program; global variables are static. Stack storage comes and goes as functions are called ("stack frames"), and heap storage is allocated and deallocated using operators new and delete. Note that usage like: void f() { static int x = 37; } also refers to storage that persists throughout the program, even though x cannot be used outside of f() to refer to that storage. So we might say that "static" as a descriptive term is used to describe the lifetime of memory locations. static can also be used to describe the visibility of objects. For example: static int x = 37; static void f() {} says that x and f() are not visible outside the source file where they're defined, and void f() { static int x = 47; } says that x is not visible outside of f(). Visibility and lifetime are not the same thing; an object can exist without being visible. So far we've covered the uses of static that are found in C. C++ adds a couple of additional twists. It is possible to have static members of a C++ class: class A { public: static int x; static void f(); int y; }; int A::x = 0; void A::f() {} A static data member like A::x is shared across all object instances of A. That is, if I define two object instances: A a1; A a2; then they have the same x but y is different between them. A static data member is useful to share information between object instances. For example, in issue #010 we talked about using a specialized allocator on a per-class basis to allocate memory for object instances, and a static member "freelist" was used as part of the implementation of this scheme. A static function member, such as A::f(), can be used to provide utility functions to a class. For example, with a class representing calendar dates, a function that tells whether a given year is a leap year might best be represented as a static function. The function is related to the operation of the class but doesn't operate on particular object instances (actual calendar dates) of the class. Such a function could be made global, but it's cleaner to have the function as part of the Date package: class Date { static int is_leap(int year); // use bool if available public: // stuff }; In this example, is_leap() is private to Date and can only be used within member functions of Date, instead of by the whole program. static meaning "local to a file" has been devalued somewhat by the introduction of C++ namespaces; the draft standard states that use of static is deprecated for objects in namespace scope. For example, saying: static int x; static void f() {} is equivalent to: namespace { int x; void f() {} } That is, an unnamed namespace is used to wrap the static declarations. All unnamed namespaces in a single source file (translation unit) are part of the same namespace and differ from similar namespaces in other translation units. LOCAL STATICS AND CONSTRUCTORS/DESTRUCTORS There's one additional interesting angle on the use of static. Suppose that you have: class A { public: A(); ~A(); }; void f() { static A a; } This object has a constructor that must be called at some point. But we can't call the constructor each time that f() is called, because the object is static, that is, exists for the life of the program, and should be constructed only once. The draft standard says that such an object should be constructed once, the first time execution passes through its declaration. This might be implemented internally by a compiler as: void f() { static int __first = 1; static A a; if (__first) { a.A::A(); // conceptual, not legal syntax __first = 0; } // other processing } If f() is never called, then the object will not be constructed. If it is constructed, it must be destructed when the program terminates. INTRODUCTION TO STREAM I/O PART 6 - SEEKING IN FILES In earlier issues we talked about streambufs, the underlying buffer used in I/O operations. One of the things that you can do with a buffer is position its pointer at various places. For example: #include int main() { ofstream ofs("xxx"); if (!ofs) ; // give error ofs << ' '; ofs << "abc"; streampos pos = ofs.tellp(); ofs.seekp(0); ofs << 'x'; ofs.seekp(pos); ofs << "def\n"; return 0; } Here we have an output file stream attached to a file "xxx". We open this file and write a single blank character at the beginning of it. In this particular application this character is a status character of some sort that we will update from time to time. After writing the status character, we write some characters to the file, at which point we wish to update the status character. To do this, we save the current position of the file using tellp(), seek to the beginning, write the character, and then seek back to where we were, at which point we can write some more characters. Note that "streampos" is a defined type of some kind rather than simply a fixed fundamental type like "long". You should not assume particular types when working with file offsets and positions, but instead save the value returned by tellp() and then use it later. In a similar way, it's tricky to use absolute file offsets other than 0 when seeking in files. For example, there are issues with binary files and with CR/LF translation. You may be assuming that a newline takes two characters when it only takes one, or vice versa. seekp() also has a two-parameter version: ofs.seekp(pos, ios::beg); // from beginning ofs.seekp(pos, ios::cur); // from current position ofs.seekp(pos, ios::end); // from end As we've said before, this area is in a state of flux, pending standardization. So future usage may be somewhat different than shown here. USING C++ AS A BETTER C PART 11 - JUMPING PAST INITIALIZATION As we've seen in several examples in previous newsletters, C++ does much more with initializing objects than C does. For example, class objects have constructors, and global objects can have general initializers that cannot be evaluated at compile time. Another difference between C and C++ is the restriction C++ places on transferring control past an initialization. For example, the following is valid C but invalid C++: #include int main() { goto xxx; { int x = 0; xxx: printf("%d\n", x); } return 0; } With one compiler, compiling and executing this program as C code results in a value of 512 being printed, that is, garbage is output. Thus the restriction makes sense. The use of goto statements is best avoided except in carefully structured situations such as jumping to the end of a block. Jumping over initializations can also occur with switch/case statements. INTRODUCTION TO TEMPLATES PART 3 - TEMPLATE ARGUMENTS In previous issues we talked about setting up a class template: template class A { T x; // stuff }; When this template is used: A a; the type "double" gets bound to the formal type parameter T, part of a process known as instantiation. In this example, the instantiated class will have a data member "x" of type double. What sorts of arguments can be used with templates? Type arguments are allowed, including "void": template class A { T* x; }; A a; The member x will be of type void* in this case. In the earlier example, using void as a type argument would result in an instantiation error, because a data member of a class (or any object for that matter) cannot be of type void. Usage like: A a1; A< A > a2; is also valid. Note that in the second example, the second space is required, because ">>" is an operator meaning right shift. It's also possible to have non-type arguments. Constant expressions can be used: template class A { T vec[n]; }; A a; This is useful in specifying the size of an internal data structure, in this example a vector of float[100]. The size could also be specified via a constructor, but in that case the size would not be known at compile time and therefore dynamic storage would have to be used to allocate the vector. The address of an external object can be used: template struct A { /* ... */ }; char c; A<&c> a; or you can use the address of a function: template struct A { /* ... */ }; void f(int) {} A a; This latter case might be useful if you want to pass in a pointer of a function to be used internally within the template, for example, to compare elements of a vector. Some other kinds of constructs are not permitted as arguments: - a constant expression of floating type - addresses of array elements - addresses of non-static class members - local types - addresses of local objects Two template classes (a template class is an instantiated class template, that is, a template with specific arguments) refer to the same class if their template names are identical and in the same scope and their arguments have identical values. For example: template struct A {}; typedef short* TT; A a1; A a2; a1 and a2 are of the same type. ACKNOWLEDGEMENTS Thanks to Nathan Myers, Eric Nagler, David Nelson, Terry Rudd, Jonathan Schilling, and Clay Wilson for help with proofreading. SUBSCRIPTION INFORMATION / BACK ISSUES To subscribe to the newsletter, send mail to majordomo@world.std.com with this line as its message body: subscribe c_plus_plus Back issues are available via FTP from: rmii.com /pub2/glenm/newslett or on the Web at: http://www.rmii.com/~glenm There is also a Java newsletter. To subscribe to it, say: subscribe java_letter using the same majordomo@world.std.com address. ------------------------- Copyright (c) 1996 Glen McCluskey. All Rights Reserved. This newsletter may be further distributed provided that it is copied in its entirety, including the newsletter number at the top and the copyright and contact information at the bottom. Glen McCluskey & Associates Professional C++ Consulting Internet: glenm@glenmccl.com Phone: (800) 722-1613 or (970) 490-2462 Fax: (970) 490-2463 FTP: rmii.com /pub2/glenm/newslett (for back issues) Web: http://www.rmii.com/~glenm