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.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru