An excellent way of transforming your world to a "vale of tears"
is to directly use the pre-defined data types in declarations. If it
is later necessary, due to portability problems, to change the return
type of a function, it may be necessary to make change at a large number
of places in the code. One way to avoid this is to declare a new type
name using classes or typedef
s to represent the types of
variables used. In this way, changes can be more easily made. This may
be used to give data a physical unit, such as kilogram or meter. Such
code is more easily reviewed. (For example, when the code is functioning
poorly, it may be noticed that a variable representing meters has been
assigned to a variable representing kilograms). It should be noted that
a typedef
does not create a new type, only an alternative
name for a type. This means that if you have declared typedef
int Error
, a variable of the type Error
may be used
anywhere that an int
may be used.
See also chapter 12, Rec. 49!
// Instead of: long int time; short int mouseX; char* menuName; // Use (for example): typedef long int TimeStamp; typedef short int Coordinate; class String { /* ... */ }; // and: TimeStamp time; Coordinate mouseX; String menuName;
int
and a long
have the same size.
int
is 32 bits long (it may be only 16 bits long).
char
is signed
or unsigned
.
char
to unsigned
if 8-bit ASCII is used.
In the definition of the C++ language, it has not yet been decided if
a char
is signed
or unsigned
. This
decision has instead been left to each compiler manufacturer. If this is
forgotten and this characteristic is exploited in one way or another, some
difficult bugs may appear in the program when another compiler is used.
If 8-bit ASCII is used (as is quite likely in the future)
and comparisons are made of two characters, it is important that
unsigned char
is used.
A processor architecture often forbids data of a given size to be
allocated at an arbitrary address. For example, a word must begin on an
"even" address for MC680x0. If there is a pointer to a char
which is located at an "odd" address, a type conversion from this
char
pointer to an int
pointer will cause the
program to crash when the int
pointer is used, since this
violates the processor's rules for alignment of data.
long
s, float
s, double
s or
long double
s may begin at arbitrary addresses.
The representation of data types in memory is highly machine-dependent. By allocating data members to certain addresses, a processor may execute code more efficiently. Because of this, the data structure that represents a class will sometime include holes and be stored differently in different process architectures. Code which depends on a specific representation is, of course, not portable.
See 18.3 for explanation of Port. Rec. 10.
If a value is modified twice in the same expression, the result of the expression is undefined except when the order of evaluation is guaranteed for the operators that are used.
The order of initialization for static objects may present problems. A static object may not be used in a constructor, if it is not initialized until after the constructor is run. At present, the order of initialization for static objects, which are defined in different compilation units, is not defined. This can lead to errors that are difficult to locate (see Example 69). There are special techniques for avoiding this. See Example 29!
#include <iostream.h> class X { public: X(int y); private: int i; int j; }; inline X::X(int y) : j(y), i(j) // No! j may not be initialized before i !! { cout << "i:" << i << " & " << "j:" << j << endl; } main() { X x(7); // Rather unexpected output: i:0 & j:7 }
// Foo.hh #include <iostream.h> #include <string.h> static unsigned int const Size = 1024; class Foo { public: Foo( char* cp ); // Constructor // ... private: char buffer[Size]; static unsigned counter; // Number of constructed Foo:s }; extern Foo foo_1; extern Foo foo_2; // Foo1.cc #include "Foo.hh" unsigned Foo::counter = 0; Foo foo_1 = "one"; //Foo2.cc #include "Foo.hh" Foo foo_2 = "two"; Foo::Foo( char* cp ) // Irrational constructor { strncpy( buffer, cp, sizeof(buffer) ); foos[counter] = this; switch ( counter++ ) { case 0: case 1: cout << ::foo_1.buffer << "," << ::foo_2.buffer << endl; break; default: cout << "Hello, world" << endl; } } // If a program using Foo.hh is linked with Foo1.o and Foo2.o, either // ,two or one, is written on standard output depending on // one,two one,two the order of the files given to the linker.
Temporary objects are often created in C++, such as when functions return a value. Difficult errors may arise when there are pointers in temporary objects. Since the language does not define the life expectancy of temporary objects, it is never certain that pointers to them are valid when they are used.
One way of avoiding this problem is to make sure that temporary objects are not created. This method, however, is limited by the expressive power of the language and is not generally recommended.
The C++ standard may someday provide an solution to this problem. In any case, it is a subject for lively discussions in the standardization committee.
class String { public: operator const char*() const; // Conversion operator to const char* friend String operator+( const String& left, const String& right ); // ... }; String a = "This may go to "; String b = "h***!"; // The addition of a and b generates a new temporary String object. // After it is converted to a char* by the conversion operator, it is // no longer needed and may be deallocated. This means that characters // which are already deallocated are printed to cout -> DANGEROUS!! cout << a + b;
Pointer arithmetic can be portable. The operators ==
and !=
are defined for all pointers of the same type,
while the use of the operators <
, >
,
<=
, >=
are portable only if they are
used between pointers which point into the same array.