13.7 Style
Programming
style for classes looks pretty much like the style for structures and
functions. Every member variable should be followed by a comment
explaining it, and every member function should be commented like a
function.
However, you comment the prototypes for member functions differently
from normal function prototypes. For normal functions you put a full
function comment block in front for the prototype. If you did this
for the member functions of a class, the comments would obscure the
structure of the class. This is one of the few cases when too many
comments can cause trouble. So you put a one-line comment in front of
each member function prototype and full comments in front of the
function itself.
But what about inline-member functions, where the entire body of the
function is declared inside the class? How do you comment that? If
you put in full comments, you obscure the structure of the class. If
you put in one-liners, you omit a lot of useful information. Proper
commenting is a balancing act. You need to put in
what's useful and leave out what's
not.
The solution is to keep the size of the inline-member function small.
There are two reasons for this: first, all inline functions should be
small, and second, large functions declared inside a class make the
class excessively complex. A good rule of thumb is that if the
function requires more than about five lines of code, put a prototype
in the class and put the body of the function elsewhere.
The structure of very small member functions should be obvious and
thus not require a full-blown comment block. If the function is not
obvious and requires extensive comments, you can always put in a
prototype and comment the body of the function later in the program.
C++ does not require an access specifier (public, private, or protected) before the first member
variable. The following is perfectly legal:
class example {
int data;
// ...
};
But what is the access protection of data? Is it
public, private, or protected? If you put in an explicit
specification, you don't have to worry about
questions like this. (For those of you who are curious, the access
specification defaults to private.)
Finally, C++ will automatically generate some member functions, such
as the default constructor, the copy constructor, and the assignment
operator. Suppose you have a class that does not specify a copy
constructor, such as this:
// Comments describing the class
// Note: The style of this class leaves something to be desired
class queue {
private:
int data[100]; // Data stored in the queue
int first; // First element in the queue
int last; // Last element in the queue
public:
queue( ); // Initialize the queue
void put(int item);// Put an item in the queue
int get( ); // Get an item from the queue
};
Did the programmer who created this class forget the copy
constructor? Will the copy constructor automatically generated by C++
work, or did the programmer design this class knowing that the copy
constructor would never be called? These important questions are not
answered by the class as written.
All classes have a default constructor, copy constructor, assignment
operator, and destructor. If you do not create one of these special
member functions, C++ will generate one automatically for you. If you
expect to use any of these automatic functions, you should put a
comment in the class to indicate the default is being used:
// Comments describing the class
class queue {
private:
int data[100]; // Data stored in the queue
int first; // First element in the queue
int last; // Last element in the queue
public:
queue( ); // Initialize the queue
// queue(const queue& old_queue)
// Use automatically generated copy constructor
// queue operator = (const queue& old_queue)
// Use automatically generated assignment operator
// ~queue( )
// Use automatically generated destructor
void put(int item);// Put an item in the queue
int get( ); // Get an item from the queue
};
Now it is obvious what member functions the programmer wanted to let
C++ generate automatically, and being obvious is very important in
any programming project.
The copy constructor automatically generated by C++ is rather simple
and limited. It doesn't work in all cases, as
you'll see later when you start to construct more
complex classes. But what happens when the automatic copy constructor
won't work as you desire, and you
don't want to go to the trouble to create your own?
After all, you may decide that a class will never be copied (or that
if it is, it's an error).
One solution is to create a dummy copy constructor that prints an
error message and aborts the program:
class no_copy {
// Body of the class
public:
// Copy constructor
no_copy(const no_copy& old_class) {
std::cerr <<
"Error: Copy constructor for 'no_copy' called. Exiting\n";
exit(8);
}
};
This works, sort of. The problem is that errors are detected at
runtime instead of compile time. You want to catch errors as soon as
possible, so this solution is at best a hack.
However, you can prevent the compiler from automatically calling the
copy constructor. The trick is to declare it private.
That's your way of saying to the world,
"Yes, there is a copy constructor, but no one can
ever use it":
class no_copy {
// Body of the class
private:
// There is no copy constructor
no_copy(const no_copy& old_class);
};
Now when the compiler attempts to use the copy constructor, you will
get an error message like "Error: Attempt to access
private member function."
Note that in this example, you have defined the prototype for a copy
constructor, but no body. Since this function is never called, a body
is not needed.
|