Saturday, May 31, 2008

C++ #13

21. Don't Confuse delete With delete []
There's a common myth among Visual C++ programmers that it's OK to use delete instead of delete [] to release arrays built-in types. For example,
int *p = new int[10];
delete p; // very bad; should be: delete[] p

This is totally wrong. The C++ standard specifically says that using delete to release dynamically allocated arrays of any type—including built-in types—yields undefined behavior. The fact that on some platforms apps that use delete instead of delete [] don't crash can be attributed to sheer luck: Visual C++, for example, implements both delete and delete [] for built-in types by calling free(). However, there is no guarantee that future releases of Visual C++ will adhere to this convention. Furthermore, there's no guarantees that this will work with other compilers. To conclude, using delete instead of delete[] and vice versa is a very bad programming habit that should be avoided.

22. Restrictions on Using Memcpy()
You should use memcpy() only for copying POD (plain old data) types, but not class objects. For copying objects, use their assignment operator or the std::copy() algorithm. Remember also that the pointers passed to memcpy() must point to non-overlapping variables; otherwise, the results are undefined:

struct A
{
int n;
};

A a;
memcpy(&a, &a, sizeof(A));//overlapping pointers; undefined

23. Fast, Efficient and Easy Way to Initialize Local Arrays
Fast, Efficient and Easy Way to Initialize Local Arrays By default, local data arrays created on the stack are not initialized; they contain garbage values. The easiest, most efficient and future-proof way to initialize them is as follows:

void f() {
char name[100] = {0}; //all members initialized to '\0'
float farr[1024] = {0}; //all members initialized to 0.0
int iarr[9999] = {0}; //all members initialized to 0.0
void *pvarr[100] = {0};//all members initialized to NULL
}//f()
And it even works for arrays of structs:

struct A { char name[20];
int age; /*other members*/
};

void f() {
A a[100] = {0};
//...

}//f()

The only exception is arrays of objects, which cannot be initialized like this if they have a user-defined constructor.
24. The calloc() Function
The standard C library declares the function calloc() in as follows:

void *calloc(size_t elements, size_t sz);
calloc() allocates space for an array of elements, each of which occupies sz bytes of storage. The space of each element is initialized to binary zeros. In other words, calloc() is similar to malloc(), except that it handles arrays of objects rather than a single chunk of storage and that it initializes the storage allocated. The following example allocates an array of 100 int's using calloc():

int * p = (int*) calloc (100, sizeof(int));
Remember that in C++, you have better alternatives to calloc() and malloc(), namely new and new [], and that these C functions should only be used for allocating POD (Plain Old Data) objects; never class objects. However, if you're using C or maintaining legacy C code, you might get across this function.
25. How to Delete Dynamically Allocated Multidimensional Arrays
You can allocate a multidimensional array using new as follows:
class A
{
public:
int j;
//...constructor, destructor, etc.
}

void func()
{
int m;
A (*pa)[2][2]=new A[2][2][2]; // three dimensional array
m=pa[0][0][0].j; // access member of array's first element
m=pa[1][1][1].j; // access member of array's last element
}
The function func() allocates a three-dimensional array of A objects called pa. How do you delete a dynamically-allocated multidimensional array? It's simple: no matter how many dimensions the array has, you always use delete[] to destroy it, as follows:

delete[] pa;
26. Use Register variables to enhance performance
The C/C++ register keyword can be used as a hint to the compiler that the declared variable is to be accessed very often during program execution, and hence, should be stored on a machine register instead of RAM, in order to enhance performance. A good example is a loop control variable. When not stored in a register, a significant amount of the loop's execution time is dedicated to dealing with fetching the variable from memory, assigning a new value to it, and storing it back in memory over and over again. Storing it in a machine register can improve performance significantly:

void my_memset(void * pbuf, size_t bufsize, char assigned_val = `\0')
{
char *p = static_cast<char *> (pbuf);
if (pbuf && bufsize)
{
for (register int i = 0; i < bufsize; i++)
* p++ = assigned_val;
}
return;
};
When using register variables please note:
1. A register declaration is only a "recommendation" to the compiler, it may be ignored.
2. The address of a register variable may not be taken.
3. The register keyword can be used for types other than int. In that case, it serves as a hint to the compiler to store the variable in the fastest memory location (for example, cache memory).
4. Some compilers ignore the register recommendation and automatically store variables in registers according to a set of built-in optimization rules.
Virtual Functions
27. Return type of an overriding virtual member function
Once declared virtual, a member function can be overridden in any level of derivation, as long as the overriding version has the identical signature of the original declaration. This is quite straightforward, but sometimes a virtual has to return an object of type that differs from the return type declared in its base class. The C++ Standard allows that only when the return type is replaced with a class publicly derived from the original return type. For example:

class B {
virtual B* clone () const { return new B; }
}

class D {
void* clone () const { return new B; } //error; return type
//differs

D* clone () const { return new D; } //OK, D is publicly
//derived from B

}

28. Default Arguments in Virtual Functions Must be Identical

You should pay attention when using default arguments in virtual functions. The default values in the overriding function have to be identical to the corresponding default values in the base class. Otherwise, unexpected results may occur:

class Base
{
public:
virtual void say (int n = 0) const { cout << "Base::say() : " << n << endl; };
};

class Derived: public Base
{
public:
virtual void say (int n = 5) const { cout << "Derived::say() : " << n << endl; };
};

int main()
{
Derived d;
Base* basePtr = &d;
basePtr->say(); // out put is Derived::say() : 0
}
It should be noted that unlike virtual functions, which are resolved at run time, default arguments are resolved statically, that is, at compiled time. In the example above, the static type of p is "pointer to Base". The compiler therefore supplies 0 as a default argument in a function call. However, the dynamic type of p is "pointer to Derived", which takes 5 as a default argument.
Since a base class and a derived class may be compiled separately, most compilers cannot detect such inconsistencies. Therefore, the programmer is responsible for making sure that the default values of a virtual function in all levels of derivation match exactly.
29. Inlining Virtual Member Functions
Generally, compilers can't inline a virtual function call if the it's resolved dynamically. Therefore, declaring a virtual member function inline might seem pointless. However, not every call of a virtual function is resolved dynamically; in some cases, the compiler can resolve the call statically, as if the function weren't virtual. In situations like these, the compiler can also inline the call. For example:

class Base
{
public:
inline virtual int f() { return 0; }
};

int main()
{
Base b;
b.f(); // resolved statically; call can be inlined
}
The invocation of f() is resolved statically because b is not a pointer or a reference. The compiler can also expand the call inline, thereby optimizing the code even further.

No comments: