Issue #021 March, 1997 Contents: Pointers to Members A New Angle on Function Pointers Notes From ANSI/ISO - Clarifications on Exception Handling Introduction to STL Part 8 - advance() and distance() POINTERS TO MEMBERS In ANSI C, function pointers are used like this: #include void f(int i) { printf("%d\n", i); } typedef void (*fp)(int); void main() { fp p = &f; (*p)(37); /* these are equivalent */ p(37); } and are employed in a variety of ways, for example to specify a comparison function to a library function like qsort(). In C++, pointers can be similarly used, but there are a couple of quirks to consider. We will discuss two of them in this section, and another one in the next section. The first point to mention is that C++ has C-style functions in it, but also has other types of functions, notably member functions. For example: class A { public: void f(int); }; In this example, A::f(int) is a member function. That is, it operates on object instances of class A, and the function itself has a "this" pointer that points at the instance in question. Because C++ is a strongly typed language, it is desirable that a pointer to a member function be treated differently than a pointer to a C-style function, and that a pointer to a function member of class A be distinguished from a pointer to a member of class B. To do this, we can say: #include class A { public: void f(int i) {cout << "value is: " << i << "\n";} }; typedef void (A::*pmfA)(int); pmfA x = &A::f; void main() { A a; A* p = &a; (p->*x)(37); } Note the notation for actually calling the member function. It is not possible to intermix such a type with other pointer types, so for example: void f(int) {} pmfA x = &f; is invalid. A static member function, as in: class A { public: static void g(int); }; typedef void (*fp)(int); fp p = &A::g; is treated like a C-style function. A static function has no "this" pointer and does not operate on actual object instances. Pointers to members are typically implemented just like C function pointers, but there is an issue with their implementation in cases where inheritance is used. In such a case, you have to worry about computing offsets of subobjects, and so on, when calling a member function, and for this purpose a runtime structure similar to a virtual table used for virtual functions is used. It's also possible to have pointers to data members of a class, with the pointer representing an offset into a class instance. For example: #include class A { public: int x; }; typedef int A::*piA; piA x = &A::x; void main() { A a; A* p = &a; a.x = 37; cout << "value is: " << p->*x << "\n"; } Note that saying "&A::x" does not take the address of an actual data member in an instance of A, but rather computes a generic offset that can be applied to any instance. A NEW ANGLE ON FUNCTION POINTERS The discussion on function pointers in this issue overlooks one key angle that has fairly recently been introduced into the language. This involves distinguishing between C and C++ pointers. A C-style pointer in C++, that is, one that does not point to a member function, is used just like a function pointer in C. But according to the standard (section 7.5), such a pointer in fact has a different type. For example, consider: extern "C" typedef void (*fp1)(int); extern "C++" typedef void (*fp2)(int); extern "C" void f(int); fp1 and fp2 are not the same type, and saying: fp2 p = &f; to initialize p to the f(int) declared in the 'extern "C"' will not work. It is possible to overload functions on this basis, so that for example: extern "C" void f(void (*)(int)); extern "C++" void f(void (*)(int)); is legal, with the appropriate f() called based on the function pointer type passed to it. The function pointer parameter types in this example are not identical; the first is a pointer to a C function, the second a pointer to a C++ one. This feature is new and may not be implemented in your local C++ compiler. NOTES FROM ANSI/ISO - CLARIFICATIONS ON EXCEPTION HANDLING Jonathan Schilling, jls@sco.com The ANSI/ISO C++ standards meeting earlier this month in Nashua, New Hampshire, produced some clarifications of exception handling semantics. One interesting case is this one, which was featured in the January 1997 issue of the magazine C++ Report: try { // exception prone code here, that may do a throw } catch (...) { // common error code here try { throw; // re-throw to more specific handler } catch (ExceptA&) { // handle ExceptA here } catch (ExceptB&) { // handle ExceptB here } catch (...) { // handle unknown exceptions here } throw; } The idea behind the code is to factor out common error handling logic into the first part of the catch handler (so as not to replicate it), rethrow the exception to get error handling specific to the exception in the individual inner handlers, and then finally to rethrow the exception again to let functions further up the call chain do their handling. The question is, does this code work as intended? The draft standard speaks of a throw creating a temporary object that is then deleted when the corresponding handler exits. Does this mean that when the inner handlers above exit, the rethrow will be of a nonexistent temporary object? The standard isn't really clear on this, and some existing compilers have been found to do the deletion at the inner handler, with the result that the program crashes. The answer is that this code should indeed work as intended, and that the existing compilers for which this does not work are wrong. (Fortunately SCO's new C++ compiler is one of the ones that is getting it right!). Furthermore, the committee stated that the value of the standard library function uncaught_exception() (see C++ Newsletter #019) changes (from false to true) at both of the rethrows, until such time as the rethrown exception is caught again. Another exception handling issue that was clarified is whether base class destructors are called when a derived class destructor throws an exception: class B { public: ~B() { ... } }; class D : public B { public: ~D() { throw "error"; } }; void f() { try { D d; } catch (...) { } } Does ~B() get called as well as ~D()? The answer is yes. This may seem almost obvious -- it is part of the general principle of C++ that constructed subobjects always get destroyed if something goes wrong with the enclosing object -- but in fact there was some debate on this within the committee. Finally, one of the comments from the ANSI public review period concerned an area of exception handling that needed no clarification but is often misunderstood: try { throw 0; } catch (void *) { // does the exception get caught here? } The handler should not catch the exception, but apparently in some compilers it does. The draft standard is clear that throw and catch types either have to match exactly, or be related by inheritance, or be subject to a pointer-to-pointer standard conversion. Since 0 is not of a pointer type, the last requirement isn't met, and no handler is found. Similarly note that the whole range of other standard conversions do not apply, so that for example a handler of type long does not catch an exception of type int. INTRODUCTION TO STL PART 8 - ADVANCE() AND DISTANCE() In the last issue we started discussing iterators. They are used in the Standard Template Library to provide access to the contents of data structures, and to cycle across multiple data elements. We presented two examples of iterator usage, the first involving pointers, the second a higher-level construct. Both of these examples require some grasp of pointer arithmetic, a daunting subject. There's another way to write the example we presented before, using a couple of STL iterator functions: #include #include #include #include using namespace std; const int N = 100; void main() { vector iv(N); iv[50] = 37; iv[52] = 47; vector::iterator iter = find(iv.begin(), iv.end(), 37); if (iter == iv.end()) { cout << "not found\n"; } else { int d = 0; distance(iv.begin(), iter, d); //cout << "found at " << iter - iv.begin() << "\n"; cout << "found at " << d << "\n"; } advance(iter, 2); cout << "value = " << *iter << "\n"; } The function distance() computes the distance between two iterator values. In this example, we know that we're starting at "iv.begin()", the beginning of the integer vector. And we've found a match at "iter", and so we can use distance() to compute the distance between these, and display this result. Note that more recently distance() has been changed to work more like a regular function, with the beginning and ending arguments supplied and the difference returned as the result of the function: d = distance(iv.begin(), iter); A similar issue comes up with advancing an iterator. For example, it's possible to use "++" for this, but cumbersome when you wish to advance the iterator a large value. Instead of ++, advance() can be used to advance the iterator a specified number of positions. In the example above, we move the iterator forward 2 positions, and then display the value stored in the vector at that location. These functions provide an alternative way of manipulating iterators, that does not depend so much on pointer arithmetic. ACKNOWLEDGEMENTS Thanks to Nathan Myers, Eric Nagler, David Nelson, and Jonathan Schilling for help with proofreading. SUBSCRIPTION INFORMATION / BACK ISSUES To subscribe to the newsletter, send mail to majordomo@world.std.com with this line as its message body: subscribe c_plus_plus Back issues are available via FTP from: rmi.net /pub2/glenm/newslett or on the Web at: http://rainbow.rmi.net/~glenm There is also a Java newsletter. To subscribe to it, say: subscribe java_letter using the same majordomo@world.std.com address. ------------------------- Copyright (c) 1997 Glen McCluskey. All Rights Reserved. This newsletter may be further distributed provided that it is copied in its entirety, including the newsletter number at the top and the copyright and contact information at the bottom. Glen McCluskey & Associates Professional C++ Consulting Internet: glenm@glenmccl.com Phone: (800) 722-1613 or (970) 490-2462 Fax: (970) 490-2463 FTP: rmi.net /pub2/glenm/newslett (for back issues) Web: http://rainbow.rmi.net/~glenm