Copy Link
Add to Bookmark
Report

C++ Newsletter/Tutorial Issue 11

eZine's profile picture
Published in 
CPPNL
 · 7 Mar 2022

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 <fstream.h> 

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 <stdio.h> 

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 T> class A { 
T x;
// stuff
};


When this template is used:

    A<double> 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 T> class A { 
T* x;
};

A<void> 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<int [37][47]> a1; 

A< A<void> > 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 T, int n> class A { 
T vec[n];
};

A<float, 100> 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 <char* cp> struct A { /* ... */ }; 

char c;

A<&c> a;


or you can use the address of a function:

    template <void (*fp)(int)> struct A { /* ... */ }; 

void f(int) {}

A<f> 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 <class T, int n> struct A {}; 

typedef short* TT;

A<short*, 100> a1;
A<TT, 25*4> 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

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT