Thursday, May 29, 2008

C++ puzzles #4

Take Advantage of the Automatic Initialization of Static Objects

By default, all static variables, structs and arrays are automatically initialized to binary zeros before program's outset. Likewise, static objects are initialized to binary zeros before their constructor is activated. Therefore, if you write a class whose objects always have static storage, you can take advantage of the automatic zero-initialization of its members and avoid explicit initialization within the constructor:

 
class Message
{
private:
  char msg[100];
public:
  Message() { for (int i =0; i<100; i++) msg[i] = '\0'; } /*unnecessary if all Message objects are static*/
  Fill (const char *text);
  const char * Read() const { return msg;}
 };
 

The constructor of Message initializes the array of characters 'msg' to binary zeros. If every Message object is static (it is declared as a local static object, a global object, or a namespace member), the constructor is unnecessary and can be removed as an optimization measure. Note that this assumption (i.e., that all Message objects are static) should be properly documented.

When Can a void-Cast be Useful?

Stringent compilers issue a warning message when a function's return value is ignored. For example:

 
int func()
{
  return 0;
}
 
int main()
{
  func(); //return value ignored
}

Indeed, ignoring a function's return value may indicate a bug. However, sometimes it is intentional and well behaved. To suppress the compiler warnings in this case, you can explicitly cast the return value to void:

 
int main()
{
  (void) func(); //suppress compiler warnings about ignored return value
}
 

Deleting a const Object

In earlier stages of C++, it was impossible to delete a const object, even if that object was constructed on the free store. This could cause memory leaks.

However, the C++ Standard was changed recently. You can now use operator delete to destroy const objects that were created by new:

 
  class Foo{};
  int main()
  {
   const char * p = new char[12]; // ptr to const char
   const Foo * const f = new Foo; // const ptr to const Foo
   delete p; // fine
   delete f; // fine
  }

Note that some existing compilers may not support this feature yet.

The Default Linkage Type of non-local const Objects

In C++ (but not in C), a const object declared in the global scope has internal linkage by default. This means that a const object which apparently looks like a global one is in fact visible only in the scope of its translation unit; it isn't visible in other translation units unless it's explicitly declared extern. For example:

 
// File a.cpp
 
const int x=0; 
 
// File b.cpp
const int x = 2;  
 
// File main.cpp
int main()
{
}

Both a.cpp and b.cpp define a const variable called x. However, the two definitions refer to distinct variables, each of which has a different value and is visible only from the scope of its source file. Now if you remove the const qualifier from the definition of both x's, recompile all the source files and re-link them, you will receive a linker error: "Public symbol x defined in both module a.obj and b.obj". This is because x has external linkage when it's not declared const. Therefore, it's visible from every source file in the project. Because x is defined twice, the linker complains about multiple definitions.

There are two lessons from this example. First, if you wish to make a const object declared in the global scope globally accessible, you must declare it extern. Secondly, never declare non-local const objects static, as in:

 
  static const int x = 0; // bad programming practice

Declaring a non-local object static is both redundant and deprecated.

Swapping Two Variables Without Using a Temporary

The classic implementation of the swap algorithm looks like this:

 
  void swap (int & i, int & j)
  {
    int temp = i;
    i = j;
    j = temp;
  }

Is it possible to swap i and j without using a third variable? Yes it is:

 
  swap(int & i, int & j)
  {
    i -= j;
    j += i; // j gets the original value of i
    i = (j - i); // i gets the original value of j
  }

However, this version isn't more efficient than the previous one (in fact, it's probably less efficient) because the compiler must generate a temporary variable to evaluate the following expression:

 
  i = (j - i); 

Although it has no practical value, this exercise may show up in your next job interview so it's worth remembering how to solve it.

Avoid Assignments Inside an If Condition

An assignment expression can appear inside an if condition:

 
  if (x = getval() )
  {
    // do someting
  }

The if condition is evaluated in two steps: first, the unconditional assignment to x takes place. Then, x is checked. The if block is executed only if x's value (after the assignment) isn't zero. Although this technique can save you a few keystrokes, it's highly dangerous and should be avoided. The problem is that one can easily mistake == for = or vice versa. For this reason, several compilers issue a warning message if you place an assignment expression inside an if condition, to draw your attention to a potential bug. If you need to assign a value and test the result, separate these two steps into two distinct statements as follows:

 
  x = getval();
  if (x)
  {
    // do someting
  }

This way, you document your intention more clearly and avoid this potential bug.

Demonstrating the Differences Between static_cast and reinterpret_cast

The operators static_cast and reinterpret_cast are similar: they both convert an object to an object of a different type. However, they aren't interchangeable. Static_cast uses the type information available at compile time to perform the conversion, making the necessary adjustments between the source and target types. Thus, its operation is relatively safe. On the other hand, reinterpret_cast simply reinterprets the bit pattern of a given object without changing its binary representation. To show the difference between the two, let's look at the following example:

 
  int n = 9;
  double d = static_cast < double > (n);

In this example, we convert an int to a double. The binary representation of these types is very different. In order to convert the int 9 to a double, static_cast needs to properly pad the additional bytes of d. As expected, the result of the conversion is 9.0. Now let's see how reinterpret_cast behaves in this context:

 
  int n = 9;
  double d = reinterpret_cast< double & > (n);

This time the results are unpredictable. After the cast, d contains a garbage value rather than 9.0. This is because reinterpret_cast simply copied the bit pattern of n into d as is, without making the necessary adjustments. For this reason, you should use reinterpret_cast sparingly and judiciously.

Precedence of the * and []Operators

The array subscript operator, [], has a higher precedence than the pointer dereference operator, *. Therefore, the following snippet declares an array of 10 pointers to char, not a pointer to an array of 10 characters:

 
char * p2 [10];

You can read the declaration as follows: because the array subscript has a higher precedence than the * operator, p2 is an array of 10 elements. The type of the elements is 'pointer to char' or char *. Now try to parse the following declaration:

 
char ** p3 [10];

Here again, operator [] has the highest precedence, therefore we know that p3 is an array with 10 elements. The type of the array's elements is 'pointer to pointer to char' or char **.

Binding a Reference to an Rvalue

In a previous tip, http://www.devx.com/free/tips/tipview.asp? Content_id=1585, I explained the concept of lvalues and rvalues. Binding a reference to an rvalue is allowed as long as the reference is const. The rationale behind this restriction is straightforward: you can't change an rvalue, and only a const reference ensures that the program doesn't try to modify an rvalue through its reference. In the following example, the function f() takes a const reference to int:

 
void f(const int & i);
 
int main()
{
 f(2); // OK
}

The program passes the rvalue 2 as an argument to f(). C++ creates a temporary object of type int with the value 2 and binds it to the const reference argument passed to f(). The temporary and its reference exist from the moment f() is called until it returns and are destroyed immediately afterwards. Note that had we declared i without the const qualifier, the function f() might have tried to change the value of its argument, thereby causing undefined behavior. The same rules apply to objects: you can bind a temporary object only to a const reference:

 
struct A{};
void f(const A& a);
int main()
{
 f(A()); // OK, binding a temporary A to a const reference
}

Comma Separated Expressions in Function Calls


Can you tell the difference between the following function calls?

 
f(a,b);
g((a,b)); // how many arguments are being passed to g?


In the first example, the function f() is called with two arguments: a and b. In the second example, the extra pair of parentheses indicates that the innermost expression is not an argument list but rather a <a href=“ http://www.devx.com/free/tips/tipview.asp?content_id=3088”>comma-separated expression</a>. Because a comma-separated expression is evaluated to its rightmost expression, g() receives only one argument, namely b.

Converting a Hexadecimal String to int


Suppose you need to convert a string containing a hexadecimal value to int. Although you can use a std::stringstream object for this purpose, it's worth noting that good old C stdio can perform this conversion just as well. The sscanf() function takes a string containing the hexadecimal value, a format string, and a variable list of pointers to which the results are written. For example:

 
char port_address[]=“0x88A9”; // string with hex value
 
#include <cstdio>
using std::sscanf;
int main()
{
 int num;
 sscanf(port_address, /*original string*/
        “%x”, /*format string; %x indicates hexadecimal*/
        &num); /*address to which the result is written*/
}


After the sscanf() call, num equals 34985 which is the decimal value of 0x88A9.

Overloading 'new' and 'delete' operators Using Hidden Arguments


You can overload 'new' and 'delete' operators, just like any other operator in C++, you just need to a hidden argument. operator 'new' requires an argument of type 'size_t' (if operator is a public function of the class), and operator 'delete' requires an argument of type 'void*'.

Example:

 
class Base {
 
public :
    void* operator new (size_t t) {
    ....
    }
    void delete (void *p) {
    ...
    }
};


The compiler needs the hidden arguments. The calls to these operators won’t need these arguments, i.e. we will call new and delete:

 
    Base *b = new Base;
    ...
    delete b;


Note here that since we have passed the arguments in the definition of the operators, the compiler will take care of them.

Pointers to const Member Functions


The const-ness of a member function is an integral part of its type. The following class defines two member functions, f(), g(), which have the same return type and parameter list:

 
class A
{
public:
 int f(int n) { return 0;}
 int g(int n) const {return 0;}
};


However, g() is a const member function, whereas f() isn't. Therefore, their types are not the same:

 
int (A::*pmf)(int)=&A::f;
int (A::*pcmf) (int)const=&A::g; // ptr to const member


You can use pcmf to call a member function of a const object. Trying to use pmf in this context will fail because it's not pointing to a const member function:

 
const A *p=new A; // p is a pointer to a const object
(p->*pcmf)(5); // fine, using ptr to const member
(p->*pmf)(5); // error, not a pointer to a const member

 

No comments: