Home arrow C++ Programming arrow Name Control in C++

Language Translator

Hacking Zone

Hacking Tools
Attacking

Configure Windows

Windows Configuration

Novels

Mix Novels

Human Personality

Body Language
Name Control in C++ PDF Print E-mail
Written by Hemanshu Patel   
Monday, 22 October 2007
Article Index
Name Control in C++
Page 2
Page 3
Page 4

 Technique one

This technique was pioneered by Jerry Schwarz while creating the iostream library (because the definitions for cin, cout, and cerr are static and live in a separate file). It’s actually inferior to the second technique but it’s been around a long time and so you may come across code that uses it; thus it’s important that you understand how it works.

This technique requires an additional class in your library header file. This class is responsible for the dynamic initialization of your library’s static objects. Here is a simple example:

//: C10:Initializer.h
// Static initialization technique
#ifndef INITIALIZER_H
#define INITIALIZER_H
#include <iostream>
extern int x; // Declarations, not definitions
extern int y;

class Initializer {
static int initCount;
public:
Initializer() {
std::cout << "Initializer()" << std::endl;
// Initialize first time only
if(initCount++ == 0) {
std::cout << "performing initialization"
<< std::endl;
x = 100;
y = 200;
}
}
~Initializer() {
std::cout << "~Initializer()" << std::endl;
// Clean up last time only
if(--initCount == 0) {
std::cout << "performing cleanup"
<< std::endl;
// Any necessary cleanup here
}
}
};

// The following creates one object in each
// file where Initializer.h is included, but that
// object is only visible within that file:
static Initializer init;
#endif // INITIALIZER_H ///:~

The declarations for x and y announce only that these objects exist, but they don’t allocate storage for the objects. However, the definition for the Initializer init allocates storage for that object in every file where the header is included. But because the name is static (controlling visibility this time, not the way storage is allocated; storage is at file scope by default), it is visible only within that translation unit, so the linker will not complain about multiple definition errors.

Here is the file containing the definitions for x, y, and initCount:

//: C10:InitializerDefs.cpp {O}
// Definitions for Initializer.h
#include "Initializer.h"
// Static initialization will force
// all these values to zero:
int x;
int y;
int Initializer::initCount;
///:~

(Of course, a file static instance of init is also placed in this file when the header is included.) Suppose that two other files are created by the library user:

//: C10:Initializer.cpp {O}
// Static initialization
#include "Initializer.h"
///:~

and

//: C10:Initializer2.cpp
//{L} InitializerDefs Initializer
// Static initialization
#include "Initializer.h"
using namespace std;

int main() {
cout << "inside main()" << endl;
cout << "leaving main()" << endl;
} ///:~

Now it doesn’t matter which translation unit is initialized first. The first time a translation unit containing Initializer.h is initialized, initCount will be zero so the initialization will be performed. (This depends heavily on the fact that the static storage area is set to zero before any dynamic initialization takes place.) For all the rest of the translation units, initCount will be nonzero and the initialization will be skipped. Cleanup happens in the reverse order, and ~Initializer( ) ensures that it will happen only once.

This example used built-in types as the global static objects. The technique also works with classes, but those objects must then be dynamically initialized by the Initializer class. One way to do this is to create the classes without constructors and destructors, but instead with initialization and cleanup member functions using different names. A more common approach, however, is to have pointers to objects and to create them using new inside Initializer( ).

 

Technique two

Long after technique one was in use, someone (I don’t know who) came up with the technique explained in this section, which is much simpler and cleaner than technique one. The fact that it took so long to discover is a tribute to the complexity of C++.

This technique relies on the fact that static objects inside functions are initialized the first time (only) that the function is called. Keep in mind that the problem we’re really trying to solve here is not when the static objects are initialized (that can be controlled separately) but rather making sure that the initialization happens in the proper order.

This technique is very neat and clever. For any initialization dependency, you place a static object inside a function that returns a reference to that object. This way, the only way you can access the static object is by calling the function, and if that object needs to access other static objects on which it is dependent it must call their functions. And the first time a function is called, it forces the initialization to take place. The order of static initialization is guaranteed to be correct because of the design of the code, not because of an arbitrary order established by the linker.

To set up an example, here are two classes that depend on each other. The first one contains a bool that is initialized only by the constructor, so you can tell if the constructor has been called for a static instance of the class (the static storage area is initialized to zero at program startup, which produces a false value for the bool if the constructor has not been called):

//: C10:Dependency1.h
#ifndef DEPENDENCY1_H
#define DEPENDENCY1_H
#include <iostream>

class Dependency1 {
bool init;
public:
Dependency1() : init(true) {
std::cout << "Dependency1 construction"
<< std::endl;
}
void print() const {
std::cout << "Dependency1 init: "
<< init << std::endl;
}
};
#endif // DEPENDENCY1_H ///:~

The constructor also announces when it is being called, and you can print( ) the state of the object to find out if it has been initialized.

The second class is initialized from an object of the first class, which is what will cause the dependency:

//: C10:Dependency2.h
#ifndef DEPENDENCY2_H
#define DEPENDENCY2_H
#include "Dependency1.h"

class Dependency2 {
Dependency1 d1;
public:
Dependency2(const Dependency1& dep1): d1(dep1){
std::cout << "Dependency2 construction ";
print();
}
void print() const { d1.print(); }
};
#endif // DEPENDENCY2_H ///:~

The constructor announces itself and prints the state of the d1 object so you can see if it has been initialized by the time the constructor is called.

To demonstrate what can go wrong, the following file first puts the static object definitions in the wrong order, as they would occur if the linker happened to initialize the Dependency2 object before the Dependency1 object. Then the order is reversed to show how it works correctly if the order happens to be “right.” Lastly, technique two is demonstrated.

To provide more readable output, the function separator( ) is created. The trick is that you can’t call a function globally unless that function is being used to perform the initialization of a variable, so separator( ) returns a dummy value that is used to initialize a couple of global variables.

//: C10:Technique2.cpp
#include "Dependency2.h"
using namespace std;

// Returns a value so it can be called as
// a global initializer:
int separator() {
cout << "---------------------" << endl;
return 1;
}

// Simulate the dependency problem:
extern Dependency1 dep1;
Dependency2 dep2(dep1);
Dependency1 dep1;
int x1 = separator();

// But if it happens in this order it works OK:
Dependency1 dep1b;
Dependency2 dep2b(dep1b);
int x2 = separator();

// Wrapping static objects in functions succeeds
Dependency1& d1() {
static Dependency1 dep1;
return dep1;
}

Dependency2& d2() {
static Dependency2 dep2(d1());
return dep2;
}

int main() {
Dependency2& dep2 = d2();
} ///:~

The functions d1( ) and d2( ) wrap static instances of Dependency1 and Dependency2 objects. Now, the only way you can get to the static objects is by calling the functions and that forces static initialization on the first function call. This means that initialization is guaranteed to be correct, which you’ll see when you run the program and look at the output.

Here’s how you would actually organize the code to use the technique. Ordinarily, the static objects would be defined in separate files (because you’re forced to for some reason; remember that defining the static objects in separate files is what causes the problem), so instead you define the wrapping functions in separate files. But they’ll need to be declared in header files:

//: C10:Dependency1StatFun.h
#ifndef DEPENDENCY1STATFUN_H
#define DEPENDENCY1STATFUN_H
#include "Dependency1.h"
extern Dependency1& d1();
#endif // DEPENDENCY1STATFUN_H ///:~

Actually, the “extern” is redundant for the function declaration. Here’s the second header file:

//: C10:Dependency2StatFun.h
#ifndef DEPENDENCY2STATFUN_H
#define DEPENDENCY2STATFUN_H
#include "Dependency2.h"
extern Dependency2& d2();
#endif // DEPENDENCY2STATFUN_H ///:~

Now, in the implementation files where you would previously have placed the static object definitions, you instead place the wrapping function definitions:

//: C10:Dependency1StatFun.cpp {O}
#include "Dependency1StatFun.h"
Dependency1& d1() {
static Dependency1 dep1;
return dep1;
} ///:~

Presumably, other code might also be placed in these files. Here’s the other file:

//: C10:Dependency2StatFun.cpp {O}
#include "Dependency1StatFun.h"
#include "Dependency2StatFun.h"
Dependency2& d2() {
static Dependency2 dep2(d1());
return dep2;
} ///:~

So now there are two files that could be linked in any order and if they contained ordinary static objects could produce any order of initialization. But since they contain the wrapping functions, there’s no threat of incorrect initialization:

//: C10:Technique2b.cpp
//{L} Dependency1StatFun Dependency2StatFun
#include "Dependency2StatFun.h"
int main() { d2(); } ///:~

When you run this program you’ll see that the initialization of the Dependency1 static object always happens before the initialization of the Dependency2 static object. You can also see that this is a much simpler approach than technique one.

You might be tempted to write d1( ) and d2( ) as inline functions inside their respective header files, but this is something you must definitely not do. An inline function can be duplicated in every file in which it appears – and this duplication includes the static object definition. Because inline functions automatically default to internal linkage, this would result in having multiple static objects across the various translation units, which would certainly cause problems. So you must ensure that there is only one definition of each wrapping function, and this means not making the wrapping functions inline.

 

Alternate linkage specifications

What happens if you’re writing a program in C++ and you want to use a C library? If you make the C function declaration,

float f(int a, char b);

the C++ compiler will decorate this name to something like _f_int_char to support function overloading (and type-safe linkage). However, the C compiler that compiled your C library has most definitely not decorated the name, so its internal name will be _f. Thus, the linker will not be able to resolve your C++ calls to f( ).

The escape mechanism provided in C++ is the alternate linkage specification, which was produced in the language by overloading the extern keyword. The extern is followed by a string that specifies the linkage you want for the declaration, followed by the declaration:

extern "C" float f(int a, char b);

This tells the compiler to give C linkage to f( ) so that the compiler doesn’t decorate the name. The only two types of linkage specifications supported by the standard are “C” and “C++,” but compiler vendors have the option of supporting other languages in the same way.

If you have a group of declarations with alternate linkage, put them inside braces, like this:

extern "C" {
float f(int a, char b);
double d(int a, char b);
}

Or, for a header file,

extern "C" {
#include "Myheader.h"
}

Most C++ compiler vendors handle the alternate linkage specifications inside their header files that work with both C and C++, so you don’t have to worry about it.

 

ummary

The static keyword can be confusing because in some situations it controls the location of storage, and in others it controls visibility and linkage of a name.

With the introduction of C++ namespaces, you have an improved and more flexible alternative to control the proliferation of names in large projects.

The use of static inside classes is one more way to control names in a program. The names do not clash with global names, and the visibility and access is kept within the program, giving you greater control in the maintenan





Digg!Reddit!Del.icio.us!Google!Live!Facebook!Slashdot!Netscape!Technorati!StumbleUpon!Spurl!Wists!Simpy!Newsvine!Blinklist!Furl!Fark!Blogmarks!Yahoo!Smarking!Netvouz!Shadows!RawSugar!Ma.gnolia!PlugIM!Squidoo!BlogMemes!FeedMeLinks!BlinkBits!Tailrank!linkaGoGo!Free social bookmarking plugins and extensions for Joomla! websites! title=
Comments
Add NewSearch
Only registered users can write comments!

Copyright (C) 2007 Alain Georgette / Copyright (C) 2006 Frantisek Hliva. All rights reserved.



 
< Prev   Next >
Your Ad Here

Donate us!!

Enter Amount:

RSS socialnet

Add to MyYahoo!
Subscribe in NewsGator Online
Add to Newsburst
Add to Google
Add to My AOL
Add to Pluck
Subscribe in FeedLounge
Add to Windows Live
Add to NetVibes
Subscribe in Rojo
Subscribe in Bloglines
Add to MyMSN
Add to Plusmo for your cellphone
Add to PageFlakes
Add to Technorati
Add to BlinkBits