Copy Link
Add to Bookmark
Report

C++ Newsletter/Tutorial Issue 12

eZine's profile picture
Published in 
CPPNL
 · 2 years ago

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 T> class String { 
// stuff
};

template <> class String<char> {
// stuff
};

String<char> 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 T> class String { 
// stuff
};

String<char> 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<char>.

For a function template, a specialization would be defined as:

    template <class T> void f(T) {} 

template <> void f(int) {}


It's also possible to have forward declarations of specializations:

    template <class T> class String {}; 

template <> class String<double>;


or:

    template <class T> void f(T) {} 

template <> void f(unsigned short&);


A specialization must be declared or defined before use, so for example:

    template <class T> 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 <class T> 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 T> class A { 
template <class U> class B {
template <class V> void f(V) {}
};
};

template <> template <> template <>
void A<float>::B<double>::f(long double) {}


and you can specialize special member types such as constructors:

    struct A { 
template <class T> A(T) {}
};

template <> A::A(short) {}


or static data members:

    template <class T> struct A { 
template <class U> struct B {
static int x;
};
};

template <> template <> int A<float>::B<long double>::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

← previous
next →
loading
sending ...
New to Neperos ? Sign Up for free
download Neperos App from Google Play
install Neperos as PWA

Let's discover also

Recent Articles

Recent Comments

Neperos cookies
This website uses cookies to store your preferences and improve the service. Cookies authorization will allow me and / or my partners to process personal data such as browsing behaviour.

By pressing OK you agree to the Terms of Service and acknowledge the Privacy Policy

By pressing REJECT you will be able to continue to use Neperos (like read articles or write comments) but some important cookies will not be set. This may affect certain features and functions of the platform.
OK
REJECT