Saturday, May 31, 2008

C++ #15

40. Casting Pointers To Members
C++ allows casting a pointer to member functions of one type to another pointer to member, as long as the signatures of the two match. However, the results of such a cast are implementation-defined. In other words, you have to consult your compiler's manual to make sure that it supports pointer to member casts. Some compilers may not support this feature and therefore, you shouldn't rely on this feature if you're writing portable code.
41. Declaring Pointers to Data Members
Although the syntax of pointers to members may seem a bit confusing at first, it is consistent and resembles the form of ordinary pointers, with the addition of the class name followed by the operator :: before the asterisk. For example, if an ordinary pointer to int looks like this:

int * pi;
you define a pointer to an int member of class A like this:

class A{/**/};
int A::*pmi; // pmi is a pointer to an int member of A
You can initialize a pointer to member:

class A
{
public:
int num;
int x;
};
int A::*pmi = &A::num; // 1
The statement numbered 1 defines a pointer to an int member of class A and initializes it with the address of the member num. Now you can use the pointer pmi to examine and modify the value of num in any object of class A:

A a1;
A a2;
int n = a1.*pmi; // copy the value of a1.num to n
a1.*pmi = 5; // assign the value 5 to a1.num
a2.*pmi = 6; // assign the value 6 to a2.num
Similarly, you can access a data member through a pointer to A:

A * pa = new A;
int n = pa->*pmi; // assign to n the value of pa->num
pa->*pmi = 5; // assign the value 5 to pa->num
Or using a pointer to an object derived from A:

class D : public A {};
A* pd = new D;
pd->*pmi = 5; // assign a value of 5 to pd->num
42. Declaring Pointers to Member Functions
Pointers to member functions consists of the member function's return type, the class name followed by ::, the pointer's name, and the function's parameter list. For example, a pointer to a member function of class A that returns int and takes no arguments is defined like this (note that both pairs of parentheses are mandatory):

class A {
public:
int func ();
};

int (A::*pmf) (); /* pmf is a pointer to some member
function of class A that returns int and takes no
arguments*/

In fact, a pointer to a member functions looks just like an ordinary pointer to function, except that it also contains the class's name immediately followed by the :: operator. You can invoke the member function to which pmf points like this:

pmf = &A::func; //assign pmf
A a;
A *pa = &a;
(a.*pmf)(); // invoke a.func()
// call through a pointer to an object
(pa->*pmf)(); // calls pa->func()

Pointers to member functions respect polymorphism. Thus, if you call a virtual member function through a pointer to member, the call will be resolved dynamically:

class Base{
public:
virtual int f (int n);
};
class Derived : public Base {
public:
int f (int h); //override
};

Base *pb = new Derived;
int (Base::*pmf)(int) = &Base::f;
(pb->*pmf)(5); // call resolved as D::f(5);

Note that you cannot take the address of a class's constructor(s) and destructor.
43. No Member-Function to Pointer-to-Member-Function Conversion
Does you compiler accept the following code?

class A
{
public:
void f(int);
};
void (A::*pm)(int) = A::f; // #1 no ampersand before A::f
A standard-compliant compiler should flag line #1 as an error. Let's examine it in further detail to see why. The expression A::f is called a qualified-id. A qualified-id denotes a member of a class. According to the C++ standard, there is no implicit conversion from a qualified-id denoting a member function to the type "pointer to member function". In other words, an ampersand must appear before the qualified-id if you want to take the address of the member function. For example:

void (A::*pm)(int) = &A::f; // now OK
Programmers who are new to the concept of pointers to members are often confused by this subtlety. After all, when dealing with ordinary functions, implicit conversion from a function type to the type "pointer to function" does exist:

void g();
void (*pf) () = g; // OK, implicit conversion of g to &g
However, there are many differences between a qualified-id and a plain function's name. Enabling implicit conversion of a qualified-id to a member function's address can therefore cause confusion and ambiguities. To avoid this, C++ requires that a class member's address be taken explicitly by preceding an ampersand to the qualified-id. Even if your compiler happens to accept the code in line #1, you should add an ampersand to avoid maintenance problems in the future, and to make your code more readable.

44. The Representation Of Pointers To Members
Although pointers to members behave very much like ordinary pointers, behind the scenes, they are not necessarily represented as pointers. In fact, a single pointer to member usually consists of a struct that contains between up to four fields, each occupying 4 bytes. This is because pointers to members have to support not only ordinary member functions, but also virtual member functions, member functions of objects that have multiple base classes, and member functions of virtual base classes. Thus, the simplest member function can be represented as a set of two pointers: one holding the physical memory address of the member function, and a second pointer that holds the this pointer. However, in cases like a virtual member function, multiple inheritance and virtual inheritance, the pointer to member must store additional information. Therefore, you cannot cast pointers to members to ordinary pointers nor can you safely cast one pointer to member to another; in both cases, the results are undefined.
To get a notion on your compiler's represents pointers to members, you can measure their size. In the following example, the sizes of a pointer to data member and a pointer to a member function are taken. As you can see, they have different sizes, hence, different representations:
struct A
{
int x;
void f();
};
int A::*pmi = &A::x;
void (A::*pmf)() = &A::f;
int n = sizeof (pmi); // 8 byte on my compiler
int m = sizeof (pmf); // 12 bytes on my compiler
45. 'Restrict' Pointers
One of the new features in the recently approved C standard C99, is the restrict pointer qualifier. This qualifier can be applied to a data pointer to indicate that, during the scope of that pointer declaration, all data accessed through it will be accessed only through that pointer but not through any other pointer. The 'restrict' keyword thus enables the compiler to perform certain optimizations based on the premise that a given object cannot be changed through another pointer. Now you're probably asking yourself, "doesn't const already guarantee that?" No, it doesn't. The qualifier const ensures that a variable cannot be changed through a particular pointer. However, it's still possible to change the variable through a different pointer. For example:

void f (const int* pci, int *pi;); // is *pci immutable?
{
(*pi)+=1; // not necessarily: n is incremented by 1
*pi = (*pci) + 2; // n is incremented by 2
}
int n;
f( &n, &n);

In this example, both pci and pi point to the same variable, n. You can't change n's value through pci but you can change it using pi. Therefore, the compiler isn't allowed to optimize memory access for *pci by preloading n's value. In this example, the compiler indeed shouldn't preload n because its value changes three times during the execution of f(). However, there are situations in which a variable is accessed only through a single pointer. For example:

FILE *fopen(const char * filename, const char * mode);
The name of the file and its open mode are accessed through unique pointers in fopen(). Therefore, it's possible to preload the values to which the pointers are bound. Indeed, the C99 standard revised the prototype of the function fopen() to the following:

/* new declaration of fopen() in <stdio.h> */
FILE *fopen(const char * restrict filename,
const char * restrict mode);
Similar changes were applied to the entire standard C library: printf(), strcpy() and many other functions now take restrict pointers:

int printf(const char * restrict format, ...);
char *strcpy(char * restrict s1, const char * restrict s2);
C++ doesn't support restrict yet. However, since many C++ compilers are also C compilers, it's likely that this feature will be added to most C++ compilers too.

46. Assigning a Specified Memory Address to a Pointer
In low-level programming and hardware interfaces, you often need to assign a pointer to a specific physical address. To do that, you have to cast the address value using the reinterpret_cast operator. Here's an example that shows how this is done:

void *p;
// assign address 0x5800FF to p
p = reinterpret_cast< void* > (0x5800FF);
47. Taking an Address of a Pointer
The sequence && is parsed as the logical AND operator. According to the Maximal Munch parsing principle, it's never construed as two & (address of) operators:

void func (char ** pp);
char c;
func (&&c); // parse error, && treated as logical AND
If you want to pass the address of c's address (i.e., a pointer to a pointer), use an auxiliary variable that holds the address of c and then pass its address to func():

char * p=&c;
func (&p); // OK, address of address of c
48. Taking an Address of a Member Function

The name of an ordinary function is implicitly converted to its memory address. For example:

typedef int (*pf) ();
int func();
pf=func; // OK, 'func' is implicitly converted to '&func'
However, member functions don't behave this way. To take their address, you must precede the address-of operator to their qualified name:

class A;
typedef int (A::*pmf)();
class A
{
pmf p; //pointer to member
public:
int func();
A() { p=&A::func;} //OK
};
Note that the following code will cause a compilation error:

A() { p=func;} // error, '&A::' missing
49. Casting To and From void*

In C++, pointers are strongly-typed. Therefore, you can't assign a void pointer to any other pointer type without casting it explicitly:

int n;
void *p = &n;
int *pi=static_cast< int* >(p);//C++ requires explicit cast

In C, the explicit typecasting is not required. By contrast, you can assign any data pointer to void* without casting:

string s;
void *p=&s; // no cast required here
50. Returning Objects by Value
For efficiency reasons, large objects should usually be passed to or returned from a function by reference or by their address (using a pointer). There are, however, a few circumstances in which the best choice is to return an object by value. A good example is an overloaded operator +. It has to return a result-object, yet it may not modify any of its operands. The seemingly natural choice is to allocate the result-object on the heap (using operator new) and return its address. But this is not such a good idea - dynamic memory allocation is significantly slower than local storage. Also it may fail and throw an exception which has to be caught and handled; worse it can lead to memory leaks since it is unclear who's responsible for deleting this object - the creator or the user? Another solution is to use a static object and return it by reference. This is also problematic, since on each invocation of the overloaded operator, the same instance of the static object is being modified and returned to the caller, resulting in aliasing. Consequently, the safest, less error prone and most efficient solution is to return the result-object by value:

class Date {
int d,m,y;
public:
Date operator + (const Date& other) const { Date temp = *this; temp += other; return temp;}
};

No comments: