Saturday, May 31, 2008

C++ #19

80. Calling a Member Function From a Destructor
It's perfectly legal to call a member function—virtual or not—from a destructor. However, you should avoid calling a member function that accesses data members that have already been destroyed. For example:

C::~C()
{
delete p;
this->serialize(p); // undefined behavior
}
In addition, the destructor shouldn't call a member function that throws an exception. Remember that throwing an exception from a destructor is a bad idea, because the destructor itself may have been invoked due to another exception (as part of the stack unwinding process). Trying to throw another exception will cause an immediate program termination.
81. Covariant Template Parameters

Suppose you need to define a function that takes a vector object and performs certain operations on it. The function has to be generic, that is, it should handle all instances of std::vector in the same way. The best way to achieve such generic behavior is by defining the function as a template whose parameter covaries with the vector's parameter. In the following example, the insert_one() function template inserts a default-initialized element to a vector, regardless of the element's type:

#include <vector>
using namespace std;

// the parameter T of insert_one() covaries with vector's T
template <class T> void insert_one(vector<T> &v)
{
T t=T(); // ensure built-in types are default-initialized
v.push_back(t);
}
int main()
{
vector <int> vi;
vector <char> vc;
insert_one(vi);
insert_one(vc);
}
82. A Pointer to Member Cannot Refer to a Static Member Function
It is illegal to assign the address of a static class member to a pointer to member. However, you can take the address of a static member function of a class and treat it as if it were an external function:

class A {
public:
static void f();
};
void main() {
void (*p) () = &A::f; //OK, p is an ordinary pointer to function
}
The secret here is that a static member function is actually an ordinary global function, whose class serves as a namespace.

STL
83. Include Templates' Definitions and Declarations in the Same Source File

Unlike ordinary classes and functions, which you declare in a header file and define in a separate .cpp file, a template's definition and its declaration must appear in the same translation unit. The reason is that the compiler generates template specializations "on demand", when the template is actually used. In order to instantiate a template specialization, the compiler must have its definition, not just the declaration, available. The simplest way to ensure that the definition of a template is always available is by placing it in the header file in which the template is declared.
84. Parenthesize Macro Arguments

It's a good idea to parenthesize every macro argument. For example:

#define MAX(x,y) (x)>(y)?(x):(y)

The parentheses ensure that the macro is evaluated correctly, even if the user passes complex expressions that contain operators as arguments:

int a=0,n=1,b=0,c=0;
int q=MAX(a+8-n, b==c?b:c)

85. Serializing a Polymorphic Object

A polymorphic object has one or more virtual functions. When you serialize such an object to a file so that it can be reconstituted later, the deserialization might rewrite its vptr and cause undefined behavior. To overcome this problem, leave the vptr intact by copying only the user-declared data members from the file. The vptr's offset within a class may vary from one compiler to another. Therefore, you need to calculate it first. The following tip explains how this can be done. Consider the following class:

class employee
{
public:
virtual promote(int rank);
//..
private:
double salary;
int rank;
int department;
bool temp;
};
employee emp;
ofstream ar("employees.dat");
// write the entire emp object to a file
ar.write(reinterpret_cast < char * > (&emp), sizeof(emp));
ar.close();

The write() member function copy all the data members of emp to a file, including the vptr. Assuming that our compiler places the vptr at offset 0, we can reconstitute the archived object as follows. First, we create an empty employee object:

employee emp2;

Then we copy the first archived data member from the file to a dummy variable:
employee emp2; ifstream arc("employees.dat"); char dummy[sizeof(void *)]; // a dummy read; advance past the vptr in the file arc.read(dummy, sizeof(void *));

The first read() call simply advanced the file's pointer past the vptr. Now we can copy the archived data members to the object:
char *p= reinterpret_cast < char * > (&emp2); // copy data to the correct offset within emp2 arc.read(p+sizeof(void*), sizeof(employee)-sizeof(void*));

If your compiler places the vptr after all user-declared data members, it's even easier:

employee emp2;
ifstream arc("employees.dat");
// copy data to the correct offset within emp2
arc.read(reinterpret_cast < char * > (&emp2),
sizeof(employee)-sizeof(void*));

86. The Minimum Requirements for STL Containment
Any object that defines the copy constructor, default constructor, assignment operator and destructor as public members (either explicitly or implicitly) can be stored in STL containers. If you do not declare any of these, the compiler will automatically define the missing member functions as public. So, if you wish to disqualify a class from STL containment, you should explicitly declare at least one of these members as protected or private.

87. STL Containers Should Not Hold auto_ptr<> Members
During reallocation, a container re-constructs its elements in a new memory location and destroys the original elements by invoking their destructor. Since the destructor of auto_ptr<> deletes its bound object, auto_ptr<> members are invalidated after a reallocation. For this reason, auto_ptr<> objects should not be stored in a container.

88. The Organization of STL Header Files: Container Classes
STL is divided into two major categories: containers and algorithms. Additional components are iterators, function objects, numeric facilities, allocators, and miscellaneous utilities. STL containers are defined in eight different header files:

<vector>
<list>
<deque>
<queue>
<stack>
<map>
<set>
<bitset>
The associative containers multimap and multiset are defined in the headers <map> and <set>, respectively. Similarly, priority_queue and deque are defined in <queue>. (On some pre-standardized implementations, the container adaptors stack, queue, and priority_queue are in <stack.h>).

89. The Organization of STL Header Files: Algorithms, Iterators, Numerics and Other
You can apply STL generic algorithms to a sequence of elements. They are defined in the header file <algorithm>. (on pre-standardized implementations, the name <algo.h> is used instead). Iterators are used to navigate sequences and are defined in the header file <iterator>.
STL also provides several classes and algorithms that are specifically designed for numeric computations. These classes and algorithms are defined in these headers:

<complex> // complex numbers and their associated operations
<valarray> // mathematical vectors and their associated operations
<numerics> // generalized numeric operations
Finally, three additional headers contain auxiliary components that are used in STL containers and algorithms, but you can also use them independently. These headers are:

<utility> // operators and pairs
<functional> // function objects
<memory> // allocators and auto_ptr
90. An Alternative STL Implementation
The STLport organization offers an alternative implementation of the Standard Template Library. You can install the alternative instead of using the existing STL implementation shipped with your compiler. The creators of this implementation claim that it's fully compatible with VC++ 5.0 and 6.0, as well as many other platforms. You can download the STLport implementation from http://www.stlport.org/index.shtml The site contains installation directions and extensive documentation about the STLport version of STL.
91. Merging Two Lists
Merging two lists isn't only a popular homework assignment; rather, this task is sometimes needed in real world programming as well. Fortunately, you don't have to reinvent the wheel anymore. STL's list defines the member function merge(), which takes a second list as an argument and merges it into the original one. If both lists were sorted, merge() ensures that the merged list remains sorted. Note that after calling merge(), the second list becomes empty. Here's an example:

#include <list>
using std::list;

int main()
{
list <int> first;
list <int> second;
// fill both lists
first.push_back(1);
first.push_back(8); // first is sorted
second.push_back(2);
second.push_back(10); // second is sorted
// merge second into first
first.merge(second);
// at this point, second is empty and first
// contains 4 elements in ascending order
}
92. Useful STL Terminology
Here are some key terms that you may find useful for reading Standard Template Library (STL) literature and documentation.
Container
A container is an object that stores objects as its elements. Normally, it's implemented as a class template that has methods for traversing, storing, and removing elements. Examples of container classes are list and vector.
Genericity
The quality of being generic, or type-independent. The above definition of the term container is too loose because it may apply to strings, arrays, and structs—which also store objects. However, a real container isn't confined to a specific data type or a subset of types. Rather, it can store any built-in or a user-defined type. Such a container is said to be generic. A generic container provides a uniform interface regardless of the actual type of elements it stores. Note that a string, for instance, can only contain characters; therefore, it's not generic. Genericity is perhaps the most important characteristic of STL.
Algorithm
A set of operations applied to an object or a sequence of object that accesses or manipulates the data therein in a specified manner and returns a result accordingly. Examples of algorithms are sort(), copy(), and remove(). STL algorithms are implemented as function templates that usually take iterators bound to a sequence of elements.
Iterator
An iterator is an object that behaves like a generic pointer. Iterators are used for traversing, adding, and removing container elements. In many ways, they behave like ordinary pointers except that they are generic and safer. Iterators are classified into subcategories that characterize their capabilities. These subcategories include input iterators, forward iterators, bidirectional iterators, and more.
Adaptor
An adaptor is a special object that can be plugged to an exiting class or function to change its behavior. For example, by plugging a special adaptor to the sort() algorithm, you can control whether the sorting order is descending or ascending. STL also defines several kinds of sequence adaptors, which transform a container to a different container with a more restricted interface. A stack, for instance, is built from a queue container and an adaptor that provides the necessary push and pop operations.
Big Oh Notation
A special notation used in performance measurements of algorithms. The STL specification imposes minimum performance limits on the operations of its algorithms and container methods. An implementation is allowed to offer better performance but not worse. In other words, the limits express the worst case scenario. The Big Oh notation enables you to evaluate the efficiency of algorithms and containers for a given operation. For instance, an algorithm such as find(), that traverses every element is a sequence (in the worst case scenario) has the following notation:

T(n) = O(n). // a linear operation

No comments: