|














Am avut 371723 vizite de la lansarea siteului.

|
|
Inapoi
Inainte
Cuprins
Reducing clutter
In a book like this, the simplicity and
terseness of putting inline definitions inside classes is very useful because
more fits on a page or screen (in a seminar). However, Dan
Saks[46]
has pointed out that in a real project this has the effect of needlessly
cluttering the class interface and thereby making the class harder to use. He
refers to member functions defined within classes using the Latin in situ
(in place) and maintains that
all definitions should be placed outside the class to keep the interface clean.
Optimization, he argues, is a separate issue. If you want to optimize, use the
inline keyword. Using
this approach, the earlier Rectangle.cpp example
becomes:
//: C09:Noinsitu.cpp
// Removing in situ functions
class Rectangle {
int width, height;
public:
Rectangle(int w = 0, int h = 0);
int getWidth() const;
void setWidth(int w);
int getHeight() const;
void setHeight(int h);
};
inline Rectangle::Rectangle(int w, int h)
: width(w), height(h) {}
inline int Rectangle::getWidth() const {
return width;
}
inline void Rectangle::setWidth(int w) {
width = w;
}
inline int Rectangle::getHeight() const {
return height;
}
inline void Rectangle::setHeight(int h) {
height = h;
}
int main() {
Rectangle r(19, 47);
// Transpose width & height:
int iHeight = r.getHeight();
r.setHeight(r.getWidth());
r.setWidth(iHeight);
} ///:~
Now if you want to compare the effect of
inline functions to non-inline functions, you can simply remove the
inline keyword. (Inline functions should normally be put in header files,
however, while non-inline functions must reside in their own translation unit.)
If you want to put the functions into documentation, it’s a simple
cut-and-paste operation. In situ functions require more work and have
greater potential for errors. Another argument for this approach is that you can
always produce a consistent formatting style for function definitions, something
that doesn’t always occur with in situ
functions.
More preprocessor features
Earlier, I said that you almost
always want to use inline functions instead of preprocessor macros. The
exceptions are when you need to use three special features in the C preprocessor
(which is also the C++ preprocessor):
stringizing, string
concatenation, and token
pasting. Stringizing, introduced
earlier in the book, is performed with the # directive and allows you to
take an identifier and turn it into a character array. String concatenation
takes place when two adjacent character arrays have no intervening punctuation,
in which case they are combined. These two features are especially useful when
writing debug code. Thus,
#define DEBUG(x) cout << #x " = " << x << endl
This prints the value of any variable.
You can also get a trace that prints out the statements as they
execute:
#define TRACE(s) cerr << #s << endl; s
The #s stringizes the statement
for output, and the second s reiterates the statement so it is executed.
Of course, this kind of thing can cause problems, especially in one-line
for loops:
for(int i = 0; i < 100; i++)
TRACE(f(i));
Because there are actually two statements
in the TRACE( ) macro, the one-line for loop executes only
the first one. The solution is to replace the semicolon with a comma in the
macro.
Token pasting
Token pasting, implemented with the
## directive, is very useful when you are manufacturing code. It allows
you to take two identifiers and paste them together to automatically create a
new identifier. For example,
#define FIELD(a) char* a##_string; int a##_size
class Record {
FIELD(one);
FIELD(two);
FIELD(three);
// ...
};
Each call to the FIELD( )
macro creates an identifier to hold a character array and another to hold the
length of that array. Not only is it easier to read, it can eliminate coding
errors and make maintenance easier.
Improved error checking
The
require.h functions have
been used up to this point without defining them (although
assert( ) has also been used to help detect
programmer errors where it’s appropriate). Now it’s time to define
this header file. Inline
functions are convenient here because they allow everything to be placed in a
header file, which simplifies the process of using the package. You just include
the header file and you don’t need to worry about linking an
implementation file.
You should note that exceptions
(presented in detail in Volume 2 of this book) provide a much more effective way
of handling many kinds of errors – especially those that you’d like
to recover from – instead of just halting the program. The conditions that
require.h handles, however, are ones which prevent the continuation of
the program, such as if the user doesn’t provide enough command-line
arguments or if a file cannot be opened. Thus, it’s acceptable that they
call the Standard C Library function
exit( ).
The following header file is placed in
the book’s root directory so it’s easily accessed from all
chapters.
//: :require.h
// Test for error conditions in programs
// Local "using namespace std" for old compilers
#ifndef REQUIRE_H
#define REQUIRE_H
#include <cstdio>
#include <cstdlib>
#include <fstream>
#include <string>
inline void require(bool requirement,
const std::string& msg = "Requirement failed"){
using namespace std;
if (!requirement) {
fputs(msg.c_str(), stderr);
fputs("\n", stderr);
exit(1);
}
}
inline void requireArgs(int argc, int args,
const std::string& msg =
"Must use %d arguments") {
using namespace std;
if (argc != args + 1) {
fprintf(stderr, msg.c_str(), args);
fputs("\n", stderr);
exit(1);
}
}
inline void requireMinArgs(int argc, int minArgs,
const std::string& msg =
"Must use at least %d arguments") {
using namespace std;
if(argc < minArgs + 1) {
fprintf(stderr, msg.c_str(), minArgs);
fputs("\n", stderr);
exit(1);
}
}
inline void assure(std::ifstream& in,
const std::string& filename = "") {
using namespace std;
if(!in) {
fprintf(stderr, "Could not open file %s\n",
filename.c_str());
exit(1);
}
}
inline void assure(std::ofstream& out,
const std::string& filename = "") {
using namespace std;
if(!out) {
fprintf(stderr, "Could not open file %s\n",
filename.c_str());
exit(1);
}
}
#endif // REQUIRE_H ///:~
The default values provide reasonable
messages that can be changed if necessary.
You’ll notice that instead of using
char* arguments, const string& arguments are used. This allows
both char* and strings as arguments to these functions, and thus
is more generally useful (you may want to follow this form in your own
coding).
In the definitions for
requireArgs( ) and requireMinArgs( ), one is added to
the number of arguments you need on the command line because argc always
includes the name of the program being executed as argument zero, and so always
has a value that is one more than the number of actual arguments on the command
line.
Note the use of local “using
namespace std” declarations within each function. This is because some
compilers at the time of this writing incorrectly did not include the C standard
library functions in namespace std, so explicit
qualification would cause a compile-time error. The local declaration allows
require.h to work with both correct and incorrect libraries without
opening up the namespace std for anyone who includes this header
file.
Here’s a simple program to test
require.h:
//: C09:ErrTest.cpp
//{T} ErrTest.cpp
// Testing require.h
#include "../require.h"
#include <fstream>
using namespace std;
int main(int argc, char* argv[]) {
int i = 1;
require(i, "value must be nonzero");
requireArgs(argc, 1);
requireMinArgs(argc, 1);
ifstream in(argv[1]);
assure(in, argv[1]); // Use the file name
ifstream nofile("nofile.xxx");
// Fails:
//! assure(nofile); // The default argument
ofstream out("tmp.txt");
assure(out);
} ///:~
You
might be tempted to go one step further for opening files and add a macro to
require.h:
#define IFOPEN(VAR, NAME) \
ifstream VAR(NAME); \
assure(VAR, NAME);
Which could then be used like
this:
IFOPEN(in, argv[1])
At first, this might seem appealing since
it means there’s less to type. It’s not terribly unsafe, but
it’s a road best avoided. Note that, once again, a macro looks like a
function but behaves differently; it’s actually creating an object
(in) whose scope persists beyond the macro. You may understand this, but
for new programmers and code maintainers it’s just one more thing they
have to puzzle out. C++ is complicated enough without adding to the confusion,
so try to talk yourself out of using preprocessor macros whenever you
can.
Summary
It’s critical that you be able to
hide the underlying implementation of a class because you may want to change
that implementation sometime later. You’ll make these changes for
efficiency, or because you get a better understanding of the problem, or because
some new class becomes available that you want to use in the implementation.
Anything that jeopardizes the privacy of the underlying implementation reduces
the flexibility of the language. Thus, the inline function is very important
because it virtually eliminates the need for preprocessor macros and their
attendant problems. With inlines, member functions can be as efficient as
preprocessor macros.
The inline function can be overused in
class definitions, of course. The programmer is tempted to do so because
it’s easier, so it will happen. However, it’s not that big of an
issue because later, when looking for size reductions, you can always change the
functions to non-inlines with no effect on their functionality. The development
guideline should be “First make it work, then optimize
it.”
Exercises
Solutions to selected exercises
can be found in the electronic document The Thinking in C++ Annotated
Solution Guide, available for a small fee from
www.BruceEckel.com.
- Write a program that uses
the F( ) macro shown at the beginning of the chapter and
demonstrates that it does not expand properly, as described in the text. Repair
the macro and show that it works
correctly.
- Write a
program that uses the FLOOR( ) macro shown at the beginning of the
chapter. Show the conditions under which it does not work
properly.
- Modify
MacroSideEffects.cpp so that BAND( ) works
properly.
- Create two
identical functions, f1( ) and f2( ). Inline
f1( ) and leave f2( ) as an non-inline function. Use the
Standard C Library function clock( ) that is found in
<ctime> to mark the starting point and ending points and compare
the two functions to see which one is faster. You may need to make repeated
calls to the functions inside your timing loop in order to get useful numbers.
- Experiment with the
size and complexity of the code inside the functions in Exercise 4 to see if you
can find a break-even point where the inline function and the non-inline
function take the same amount of time. If you have them available, try this with
different compilers and note the
differences.
- Prove
that inline functions default to internal
linkage.
- Create a
class that contains an array of char. Add an inline constructor that uses
the Standard C library function memset( ) to initialize the array to
the constructor argument (default this to ‘ ’), and an inline member
function called print( ) to print out all the characters in the
array.
- Take the
NestFriend.cpp example from Chapter 5 and replace all the member
functions with inlines. Make them non-in situ inline functions. Also
change the initialize( ) functions to
constructors.
- Modify
StringStack.cpp from Chapter 8 to use inline
functions.
- Create an
enum called Hue containing red, blue, and
yellow. Now create a class called Color containing a data member
of type Hue and a constructor that sets the Hue from its argument.
Add access functions to “get” and “set” the Hue.
Make all of the functions
inlines.
- Modify
Exercise 10 to use the “accessor” and “mutator”
approach.
- Modify
Cpptime.cpp so that it measures the time from the time that the program
begins running to the time when the user presses the “Enter” or
“Return”
key.
- Create a class
with two inline member functions, such that the first function that’s
defined in the class calls the second function, without the need for a forward
declaration. Write a main that creates an object of the class and calls the
first
function.
- Create a
class A with an inline default constructor that announces itself. Now
make a new class B and put an object of A as a member of
B, and give B an inline constructor. Create an array of B
objects and see what
happens.
- Create a
large quantity of the objects from the previous Exercise, and use the
Time class to time the difference between non-inline constructors and
inline constructors. (If you have a profiler, also try using that.)
- Write a program
that takes a string as the command-line argument. Write a for loop
that removes one character from the string with each pass, and use the
DEBUG( ) macro from this chapter to print the string each
time.
- Correct the
TRACE( ) macro as specified in this chapter, and prove that it works
correctly.
- Modify
the FIELD( ) macro so that it also contains an index number.
Create a class whose members are composed of calls to the FIELD( )
macro. Add a member function that allows you to look up a field using its index
number. Write a main( ) to test the
class.
- Modify the
FIELD( ) macro so that it automatically generates access functions
for each field (the data should still be private, however). Create a class whose
members are composed of calls to the FIELD( ) macro. Write a
main( ) to test the
class.
- Write a
program that takes two command-line arguments: the first is an int and
the second is a file name. Use require.h to ensure that you have the
right number of arguments, that the int is between 5 and 10, and that the
file can successfully be
opened.
- Write a
program that uses the IFOPEN( ) macro to open a file as an input
stream. Note the creation of the ifstream object and its
scope.
- (Challenging)
Determine how to get your compiler to generate assembly code. Create a file
containing a very small function and a main( ) that calls the
function. Generate assembly code when the function is inlined and not inlined,
and demonstrate that the inlined version does not have the function call
overhead.
[45]Andrew
Koenig goes into more detail in his book C Traps & Pitfalls
(Addison-Wesley, 1989).
[46]
Co-author with Tom Plum of C++ Programming Guidelines, Plum Hall,
1991.
 |
| |
|