Thursday, May 29, 2008

C++ puzzles #2

When Is It Safe to Use Inline?

Excessive use of inline functions might bloat the size of the executable and reduce execution speed. In other words, using inline abundantly can degrade rather than enhance performance. For this reason, many programmers avoid function inlining altogether because they can't predict how inlining will affect performance. However, there is one guarantee: very short functions (e.g., a function that merely calls another function or a function that returns a data member of its object) are always good candidates for inlining. For example:

  inline bool Foo::is_connection_alive()
    return ::connection_alive(); // calls an API function
  inline int Bar::get_x()
    return x; // merely returns a data member

By inlining such functions, the size of the executable decreases slightly and execution speed is boosted. Note, however, that this rule applies to very short functions exclusively. For larger functions, you usually need to profile the software with and without inline to assess the effects of inlining.

Default Arguments are not Part of a Function's Type

Although the default arguments of a function must appear in its declaration, they are not considered part of its type. Thus, you cannot overload a function by using different default arguments:

  void f(int n = 6);
  void f(int n = 0); //error, redefinition of f() above

The attempt to overload f() is illegal because the compiler cannot distinguish between the two versions of f().

Global Anonymous Unions Have to be Declared Static

An anonymous union declared in a named namespace or in the global namespace has to be explicitly declared static:

  static union { int num; char *pc; }; //anonymous union in global namespace
  namespace NS 
    static union { double d; bool b;}; //anonymous union in a named namespace
  int main()
    NS::d = 0.0;
    num = 5;
    pc = "str";

Performance of typeid vs. dynamic_cast<>

As far as design is concerned, dynamic_cast<> should be preferred to typeid because the former enables more flexibility and extensibility. Notwithstanding that, the runtime overhead of typeid can be less expensive than dynamic_cast<>, depending on the operands. An invocation of operator typeid is a constant time operation--it takes the same length of time to retrieve the runtime type information of every polymorphic object, regardless of the object's derivational complexity. On the other hand, dynamic_cast<> is not a constant time operation. It has to traverse the derivation tree of the operand until it has located the target object in it. The worst case scenario is when the operand is a deeply derived object and the target is a non-related class type. Then, dynamic_cast<> has to traverse the entire derivation tree before it can confidently decide that requested cast cannot be done.

The Underlying Representation of NULL

C and C++ define NULL differently:

   #define NULL 0;                        // A typical definition of NULL in C++ 
   #define NULL  ((void*)0)          // C defines NULL this way

Why is it defined differently in the two languages? Pointers in C++ are strongly typed, unlike pointers in C. Thus, void* cannot be implicitly converted to any other pointer type without an explicit cast. If C++ retained C's convention, a C++ statement such as:

   char * p = NULL; 

would be expanded into something like:

   char * p = (void*) 0;   // compile time error: incompatible pointer types 

Since 0 is the universal initializer for all pointer types in C++, it is used instead the traditional C convention, and in fact, many programmers simply use 0 as a pointer initializer.

Unconsting a const Variable

There are two types of const data storage: true const and contractual const.

  const int cn = 5; // true const

A contractual const variable is a non-const one, which is treated as though it were const:

  void ReadValue(const int& num){
  int main()  {
    int n =0;
    ReadValue(n); //contractual constness; non-const n is not modified by ReadValue()

When a true const variable is explicitly cast to a non-const one, the result of an attempt to change it is undefined. This is because an implementation may store true const data in the read-only memory (using an explicit cast to remove constness does not change the physical memory properties of a variable). For example:

  const int cnum = 0; //true const, may be stored in the machine's ROM
  const int * pci = &cnum; 
  int *pi  = const_cast<int*> (pci);     // brute force attempt to unconst a variable
  *pi = 2;    // undefined, an attempt to modify a true const variable through a pointer

On the other hand, casting away contractual constness of a variable enables you to change its value:

  int num = 0;
  const int * pci = &num;  // *pci is a contractual const int
  int *pi  = const_cast<int*> (pci);   // get rid of contractual const
  *pi = 2;    // OK, modify num's value

Interacting With the Operating System Directly

In general, API functions and classes enable you to interact with the operating system. Sometimes, however, it is much simpler to execute a system command directly. For this purpose, you can use the standard function system() that takes a const char * argument containing a shell command. For example, on a DOS/Windows system, you can display the files in the current directory like this:

#include <cstdlib>
using namespace std;
int main()
  system("dir");  //execute the "dir" command


No comments: