Thursday, May 29, 2008

C++ puzzles #10

Porting Code to a Different Compiler

I receive many questions from readers asking for guidance on how to port an existing C++ app to a different compiler or platform. Porting software can be very easy or very difficult, depending on several factors. The most important one is the extent to which the software relies on non-portable or compiler-specific features. For example, if your code uses non-standard keywords such as __closure (in Borland's C++ Builder) and __int64, you will have to replace them with the equivalent keywords supported by target compiler. Using standard constructs that have the same semantics instead of proprietary keywords is even better.

Porting is more difficult if your app is based on a proprietary framework such as MFC or DCOM which your target platform, e.g., Unix, doesn't support. In situations like these, you need to perform code analysis and separate portable code components (e.g., business logic classes, financial and arithmetic functions, standard functions and classes) from non-portable GUI and platform API components. The portable components can be used as is on the target platform. As for the non-portable components—it's likely that your target platform offers a corresponding set of classes or API functions that you can use instead. For example, instead of the spawn() family of functions used in Win32, you can use the exec() or fork() functions in Unix and Linux. Sometimes, however, you will have to redesign parts of your app from scratch.

The Memory Alignment of a Union

In C and C++, a union is aligned according to the alignment requirements of its largest member. For example:

 
union U
{
  char c;
  short s;
  int i;
};

The union U has three members, each having a different size and alignment requirement. For instance, the member c occupies only a single byte and can be aligned on a byte boundary. The member i, on the other hand, occupies 4-bytes on a 32-bit architecture and can be located at a memory address that is evenly divisible by 4. Consequently, the compiler sees that every instance of U is located at a memory address that is evenly divisible by 4.

Simulating Multidimensional Arrays Using Vectors

Although you can allocate multidimensional arrays manually, as in:

 
int (*ppi) [5]=new int[4][5]; // parentheses required 
// fill array
ppi[0][0] = 65;
ppi[0][1] = 66;
ppi[0][2] = 67;
//…

This style is tedious and error prone. First, you must parenthesize ppi to ensure that the compiler parses the declaration correctly. Secondly, you can easily bump into buffer overflow problems, and finally, you must remember to delete the allocated memory to avoid memory leaks. A much better technique for simulating multidimensional arrays is using a vector of vectors:

 
#include <vector>
#include <iostream>
using namespace std;
 
int main()
{
 vector < vector < int > > v; // two dimensions
 v.push_back(vector < int >()); // create v[0]
 v.push_back(vector < int >()); // create v[1]
 v[0].push_back(15); // assign v[0][0] 
 v[1].push_back(16); // assign v[1][0]
 cout << v[0][0]; // 15
 cout << v[1][0]; // 16
}

Because vector overloads operator [], you can use the [][] notation as if you were using a built-in two-dimensional array.

 

Deleting Multi-Dimensional Arrays Properly

You can allocate a multi-dimensional array dynamically, as in this example:

 
  int*pp[10]; //an array of ten pointers 
  for (int j=0; j<10; j++) //allocate sub-arrays
  {
    pp[j] = new int[5]; //every element in pp is a pointer to an array of 5 int's
  }
  pp[0][0] = 1; //use pp as a multi-dimensional array
  //….
 

Remember that you have to use a loop to delete a multi-dimensional array:

 
  for (int k=0; k<10; k++)
  {
    delete [] pp[k]; //delete [] must be applied to every element in the array pointer, pp
  }
 

Never use plain delete or delete[] without a loop to free the allocated storage, as in this example:

 
  delete [] pp; //very bad; undefined behavior
 

 Using an Associative Array

An associative array (also called map or dictionary) is an array for which the index does not have to be an integer. An associative array stores pairs of values. One serves as the key and the other is the associated value. The map<> container is an example of an associated array. In this example, a map is used to translate the string value of an enumerator into its corresponding integral value. The string is the key whose associated value is an int:

 
#include <map>   
#include <string>
#include <iostream>
using namespace std;
enum directions {up, down};
 
int main() 
{
  pair<string, int> Enumerator(string("down"), down); //create a pair
  map<string, int> mi; //create a map
  mi.insert(Enumerator); //insert the pair element
  int n = mi["down"]; //access the associated value of "down"; n = 1
}

The Standard Template Library (STL) defines the template pair<class Key, class Value> that serves as a map element.

Initializing a Union

It is possible to initialize a union. Yet, unlike struct initialization, the initialization list of a union must contain a single initializer that refers to the first member in the union:

 
union Key { 
  int num_key;
  void *ptr_key;
  char  name_key[10];
};
Key a_key = {5}; //first member of Key is of type int; all other bytes are initialized to binary zeroes

 

 

 

Finally we come to the end of the C++ section. I took up ten blogs to do it. I did not want any body to searc hupto here and then see a huge page. So I kept them concise for each blog.

 

Please take your time and read them , they do come in handy when you are preparing for an interview where there is a chance that you will be asked some C++ fundas.

Don’t worry , even the interviewers read from here, the clue to is to answer what he has read and not give you t some super logical and bravo type answer,

No comments: