Issue #012 June, 1996 Contents: New Language Feature - Mutable Introduction to Templates Part 4 - Specializations Using C++ as a Better C Part 12 - Function Parameter Names Performance - Duplicate Inlines NEW LANGUAGE FEATURE - MUTABLE In C++ it's possible to have a class object instance that is constant and cannot be modified by the program, once initially set up. For example: class A { public: int x; A(); }; const A a; void g() { a.x = 37; } is illegal. In a similar way, invoking a non-const member function on a const object is also illegal: class A { public: int x; A(); void f(); }; const A a; void g() { a.f(); } The reason for this latter prohibition is due to separate compilation. A::f() may be defined in some other translation unit, and there's no way of knowing whether it modifies the object upon which it operates. It is possible to define const member functions: void f() const; that are allowed to operate on a const object instance. Such a function does not modify the instance it operates on. The type of the "this" pointer for a class T is normally: T *const this; meaning that the pointer cannot be changed. Within a const member function, the type is: const T *const this; meaning that neither the pointer nor the pointed-at object instance can be modified. Recently a new feature has been added to C++ to selectively allow for individual data class members to be modified even for a const object instance, and lessen the need for casting away of const. For example: class A { public: mutable int x; A(); }; const A a; void f() { a.x = 37; } This says that "x" can be modified even though it's a member of a const object instance. How useful "mutable" turns out to be remains to be seen. One cited example for its use is within classes whose object instances appear constant but actually do change their state internally. For example: class Box { double xll, yll; // lower left X,Y double xur, yur; // upper right X,Y double a; // cached area public: double area() const { a = (xur - xll) * (yur - yll); return a; } class Box(double x1, double y1, double x2, double y2) : xll(x1), yll(y1), xur(x2), yur(y2) { } }; const Box b(1.0, 1.0, 11.0, 14.0); void f() { b.area(); } which is illegal usage unless we instead say: class Box { double xll, yll; // lower left X,Y double xur, yur; // upper right X,Y mutable double a; // cached area public: double area() const { a = (xur - xll) * (yur - yll); return a; } class Box(double x1, double y1, double x2, double y2) : xll(x1), yll(y1), xur(x2), yur(y2) { } }; const Box b(1.0, 1.0, 11.0, 14.0); void f() { b.area(); } INTRODUCTION TO TEMPLATES PART 4 - SPECIALIZATIONS In previous issues we've covered some of the basics of C++ templates. Recall that a template is a class or function skeleton, and is combined with specified type arguments to produce an actual class or function. Beyond this general mechanism, C++ also allows the programmer to define specialized classes and functions that take the template and implement it for particular types of template arguments. Suppose, for example, that you have a String template, that supports strings of most anything -- chars, ints, doubles, arbitrary class types, and so on. Now, it's pretty likely that strings of characters will be used heavily, so it might make sense to special case this combination of template and template argument: template class String { // stuff }; template <> class String { // stuff }; String x; The "template <>" notation is fairly new and may not yet be implemented in your local compiler. This sequence is a bit different from: template class String { // stuff }; String x; In this second case, the default implementation of String is used, whereas in the specialization case, the programmer overrides the default template and provides an implementation of String. For a function template, a specialization would be defined as: template void f(T) {} template <> void f(int) {} It's also possible to have forward declarations of specializations: template class String {}; template <> class String; or: template void f(T) {} template <> void f(unsigned short&); A specialization must be declared or defined before use, so for example: template int f(T) {return 0;} int i = f(12.34); template <> int f(double) {return 37;} is invalid. An interesting quirk with function templates concerns the case where you have a C function, mixed in with a function template and specialization: extern "C" void f(int); template int f(T) {return 0;} template <> int f(int) { f(37); return 0; } The f(37) call here is not recursive. Both "void f(int)" and "int f(int)" match the call, and the non-template is preferred in such a case. It's also possible to have nested specializations, as in: template class A { template class B { template void f(V) {} }; }; template <> template <> template <> void A::B::f(long double) {} and you can specialize special member types such as constructors: struct A { template A(T) {} }; template <> A::A(short) {} or static data members: template struct A { template struct B { static int x; }; }; template <> template <> int A::B::x = 59; Specializations are a way of special-casing templates for particular argument types, and are useful in a variety of applications. But they can also be abused and make code harder to understand, especially if a reader of the code doesn't pick up on the fact that specializations are present. USING C++ AS A BETTER C PART 12 - FUNCTION PARAMETER NAMES Suppose that you have a C++ function, and for some reason you don't actually use all the function parameters: int sum(int a, int b, int c) { return a + b; // c not used } Many compilers will give a warning in this case to the effect "warning: parameter c not used". This is perfectly legal code but the warning can be tedious to deal with. C++ has a feature that allows you to simply omit the parameter name: int sum(int a, int b, int) { return a + b; } and avoid the warning. This feature is especially handy when stubbing out code. A similar feature exists in catch handlers used in exception handling. PERFORMANCE - DUPLICATE INLINES Suppose that you have a bit of code such as: inline long fact(long n) { if (n < 2) return 1; else return n * fact(n - 1); } int main() { long x = fact(23); return 0; } to compute the factorial function via a recursive algorithm. Will fact() actually be expanded as an inline? In many compilers, the answer is no. The "inline" keyword is simply a hint to the compiler, which is free to ignore it. So what happens if the inline function is not expanded as inline? The answer varies from compiler to compiler. The traditional approach is to lay down a static copy of the function body, one copy for each translation unit where the inline function is used, and with such copies persisting throughout the linking phase and showing up in the executable image. Other approaches lay down a provisional copy per translation unit, but with a smart linker to merge the copies. Extra copies of functions in the executable can be quite wasteful of space. How do you avoid the problem? One way is to use inlines sparingly at first, and then selectively enable inlining based on program profiling that you've done. Just because a function is small, with a high call overhead at each invocation, doesn't necessarily mean that it should be inline. For example, the function may be called only rarely, and inlining might not make any difference to the total program execution time. Another approach diagnoses the problem after the fact. For example, here's a simple script that finds duplicate inlines on UNIX systems: #!/bin/sh nm $@ | egrep ' t ' | awk '{print $3}' | sort | uniq -c | sort -nr | awk '$1 > = 2{print}' | demangle nm is a tool for dumping the symbol tables of objects or executables. A " t " indicates a static text (function) symbol. A list of such symbols is formed and those with a count of 2 or more filtered out and displayed after demangling their C++ names ("demangle" has various names on different systems). This technique is simply illustrative and not guaranteed to work on every system. Note also that some libraries, such as the Standard Template Library, rely heavily on inlining. STL is distributed as a set of header files containing inline templates, with the idea being that the inlines are expanded per translation unit. Much of the time such an approach is perfectly acceptable, but it's worth at least knowing what's going on behind the scenes with inlining, and what you can do about it if performance is not acceptable. ACKNOWLEDGEMENTS Thanks to Nathan Myers, Eric Nagler, David Nelson, Terry Rudd, Jonathan Schilling, and Clay Wilson 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: rmii.com /pub2/glenm/newslett or on the Web at: http://www.rmii.com/~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) 1996 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: rmii.com /pub2/glenm/newslett (for back issues) Web: http://www.rmii.com/~glenm