MFC Programmer's SourceBook : Thinking in C++
Bruce Eckel's Thinking in C++, 2nd Ed Contents | Prev | Next

Inline functions

In solving the C++ problem of a macro with access to private class members, all the problems associated with preprocessor macros were eliminated. This was done by bringing macros under the control of the compiler, where they belong. In C++, the concept of a macro is implemented as an inline function, which is a true function in every sense. Any behavior you expect from an ordinary function, you get from an inline function. The only difference is that an inline function is expanded in place, like a preprocessor macro, so the overhead of the function call is eliminated. Thus, you should (almost) never use macros, only inline functions.

Any function defined within a class body is automatically inline, but you can also make a nonclass function inline by preceding it with the inline keyword. However, for it to have any effect, you must include the function body with the declaration; otherwise the compiler will treat it as an ordinary function declaration. Thus,

inline int plusOne(int x);

has no effect at all other than declaring the function (which may or may not get an inline definition sometime later). The successful approach is

inline int plusOne(int x) { return ++x; }

Notice that the compiler will check (as it always does) for the proper use of the function argument list and return value (performing any necessary conversions), something the preprocessor is incapable of. Also, if you try to write the above as a preprocessor macro, you get an unwanted side effect.

You’ll almost always want to put inline definitions in a header file. When the compiler sees such a definition, it puts the function type (signature + return value) and the function body in its symbol table. When you use the function, the compiler checks to ensure the call is correct and the return value is being used correctly, and then substitutes the function body for the function call, thus eliminating the overhead. The inline code does occupy space, but if the function is small, this can actually take less space than the code generated to do an ordinary function call (pushing arguments on the stack and doing the CALL).

An inline function in a header file defaults to internal linkage – that is, it is static and can only be seen in translation units where it is included. Thus, as long as they aren’t declared in the same translation unit, there will be no clash at link time between an inline function and a global function with the same signature. (Remember the return value is not included in the resolution of function overloading.

Inlines inside classes

To define an inline function, you must ordinarily precede the function definition with the inline keyword. However, this is not necessary inside a class definition. Any function you define inside a class definition is automatically an inline. Thus,

//: C09:Inline.cpp
// Inlines inside classes
#include <iostream>
using namespace std;

class Point {
  int i, j, k;
public:
  Point() { i = j = k = 0; }
  Point(int ii, int jj, int kk) {
    i = ii;
    j = jj;
    k = kk;
  }
  void print(const char* msg = "") const {
    if(*msg) cout << msg << endl;
    cout << "i = " << i << ", "
         << "j = " << j << ", "
         << "k = " << k << endl;
  }
};

int main() {
  Point p, q(1,2,3);
  p.print("value of p");
  q.print("value of q");
} ///:~ 

Of course, the temptation is to use inlines everywhere inside class declarations because they save you the extra step of making the external member function definition. Keep in mind, however, that the idea of an inline is to reduce the overhead of a function call. If the function body is large, chances are you’ll spend a much larger percentage of your time inside the body versus going in and out of the function, so the gains will be small. But inlining a big function will cause that code to be duplicated everywhere the function is called, producing code bloat with little or no speed benefit.

Access functions

One of the most important uses of inlines inside classes is the access function. This is a small function that allows you to read or change part of the state of an object – that is, an internal variable or variables. The reason inlines are so important with access functions can be seen in the following example:

//: C09:Access.cpp
// Inline access functions

class Access {
  int i;
public:
  int read() const { return i; }
  void set(int ii) { i = ii; }
};

int main() {
  Access A;
  A.set(100);
  int x = A.read();
} ///:~ 

Here, the class user never has direct contact with the state variables inside the class, and they can be kept private, under the control of the class designer. All the access to the private data members can be controlled through the member function interface. In addition, access is remarkably efficient. Consider the read( ), for example. Without inlines, the code generated for the call to read( ) would include pushing this on the stack and making an assembly language CALL. With most machines, the size of this code would be larger than the code created by the inline, and the execution time would certainly be longer.

Without inline functions, an efficiency-conscious class designer will be tempted to simply make i a public member, eliminating the overhead by allowing the user to directly access i. From a design standpoint, this is disastrous because i then becomes part of the public interface, which means the class designer can never change it. You’re stuck with an int called i. This is a problem because you may learn sometime later that it would be much more useful to represent the state information as a float rather than an int, but because int i is part of the public interface, you can’t change it. If, on the other hand, you’ve always used member functions to read and change the state information of an object, you can modify the underlying representation of the object to your heart’s content (and permanently remove from your mind the idea that you are going to perfect your design before you code it and try it out).

Accessors and mutators

Some people further divide the concept of access functions into accessors (to read state information from an object) and mutators (to change the state of an object). In addition, function overloading may be used to provide the same function name for both the accessor and mutator; how you call the function determines whether you’re reading or modifying state information. Thus,

//: C09:Rectangle.cpp
// Accessors & mutators

class Rectangle {
  int _width, _height;
public:
  Rectangle(int w = 0, int h = 0)
    : _width(w), _height(h) {}
  int width() const { return _width; } // Read
  void width(int w) { _width = w; } // Set
  int height() const { return _height; } // Read
  void height(int h) { _height = h; } // Set
};

int main() {
  Rectangle r(19, 47);
  // Change width & height:
  r.height(2 * r.width());
  r.width(2 * r.height());
} ///:~ 

The constructor uses the constructor initializer list (briefly introduced in Chapter XX and covered fully in Chapter XX) to initialize the values of _width and _height (using the pseudoconstructor-call form for built-in types).

Since you cannot have member function names using the same identifiers as data members, the data members are distinguished with a leading underscore (this way, the coding standard described in Appendix A can be followed, whereby all variables and functions begin with lowercase letters). Because this is a bit awkward, and because overloading this way might seem confusing, you may choose instead to use “get” and “set” to indicate accessors and mutators:

//: C09:Rectangle2.cpp
// Accessors & mutators with "get" and "set"

class Rectangle {
  int width, height;
public:
  Rectangle(int w = 0, int h = 0)
    : width(w), height(h) {}
  int getWidth() const { return width; }
  void setWidth(int w) { width = w; }
  int getHeight() const { return height; }
  void setHeight(int h) { height = h; }
};

int main() {
  Rectangle r(19, 47);
  // Change width & height:
  r.setHeight(2 * r.getWidth());
  r.setWidth(2 * r.getHeight());
} ///:~ 

Of course, accessors and mutators don’t have to be simple pipelines to an internal variable. Sometimes they can perform some sort of calculation. The following example uses the Standard C library time functions to produce a simple Time class:

//: C09:Cpptime.h
// A simple time class
#ifndef CPPTIME_H
#define CPPTIME_H
#include <ctime>
#include <cstring>

class Time {
  std::time_t t;
  std::tm local;
  char asciiRep[26];
  unsigned char lflag, aflag;
  void updateLocal() {
    if(!lflag) {
      local = *std::localtime(&t);
      lflag++;
    }
  }
  void updateAscii() {
    if(!aflag) {
      updateLocal();
      strcpy(asciiRep, std::asctime(&local));
      aflag++;
    }
  }
public:
  Time() { mark(); }
  void mark() {
    lflag = aflag = 0;
    std::time(&t);
  }
  const char* ascii() {
    updateAscii();
    return asciiRep;
  }
  // Difference in seconds:
  int delta(Time* dt) const {
    return std::difftime(t, dt->t);
  }
  int daylightSavings() {
    updateLocal();
    return local.tm_isdst;
  }
  int dayOfYear() { // Since January 1
    updateLocal();
    return local.tm_yday;
  }
  int dayOfWeek() { // Since Sunday
    updateLocal();
    return local.tm_wday;
  }
  int since1900() { // Years since 1900
    updateLocal();
    return local.tm_year;
  }
  int month() { // Since January
    updateLocal();
    return local.tm_mon;
  }
  int dayOfMonth() {
    updateLocal();
    return local.tm_mday;
  }
  int hour() { // Since midnight, 24-hour clock
    updateLocal();
    return local.tm_hour;
  }
  int minute() {
    updateLocal();
    return local.tm_min;
  }
  int second() {
    updateLocal();
    return local.tm_sec;
  }
};
#endif // CPPTIME_H ///:~ 

The Standard C library functions have multiple representations for time, and these are all part of the Time class. However, it isn’t necessary to update all of them all the time, so instead the time_t T is used as the base representation, and the tm local and ASCII character representation asciiRep each have flags to indicate if they’ve been updated to the current time_t. The two private functions updateLocal( ) and updateAscii( ) check the flags and conditionally perform the update.

The constructor calls the mark( ) function (which the user can also call to force the object to represent the current time), and this clears the two flags to indicate that the local time and ASCII representation are now invalid. The ascii( ) function calls updateAscii( ), which copies the result of the Standard C library function asctime( ) into a local buffer because asctime( ) uses a static data area that is overwritten if the function is called elsewhere. The return value is the address of this local buffer.

In the functions starting with DaylightSavings( ), all use the updateLocal( ) function, which causes the composite inline to be fairly large. This doesn’t seem worthwhile, especially considering you probably won’t call the functions very much. However, this doesn’t mean all the functions should be made out of line. If you leave updateLocal( ) as an inline, its code will be duplicated in all the out-of-line functions, eliminating the extra overhead.

Here’s a small test program:

//: C09:Cpptime.cpp
// Testing a simple time class
#include <iostream>
#include "Cpptime.h"
using namespace std;

int main() {
  Time start;
  for(int i = 1; i < 1000; i++) {
    cout << i << ' ';
    if(i%10 == 0) cout << endl;
  }
  Time end;
  cout << endl;
  cout << "start = " << start.ascii();
  cout << "end = " << end.ascii();
  cout << "delta = " << end.delta(&start);
} ///:~ 

A Time object is created, then some time-consuming activity is performed, then a second Time object is created to mark the ending time. These are used to show starting, ending, and elapsed times.

Contents | Prev | Next


Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru