const
and volatile member functions
C++
allows you to restrict the use of a particular member function so, as the
programmer, you can insure that the user can only use it in the appropriate
context. This is accomplished with the keywords
const
and volatile. You will see
const
member functions used far more often than volatile member functions, but the
syntax works the same way.
const
objects
const
tells the compiler that a variable will not change throughout its lifetime.
This applies to variables of built-in types, as you've seen. When the compiler
sees a
const
like this, it stores the value in its symbol table and inserts it directly,
after performing type-checking (remember that this is an improvement in C++,
which acts differently than C). In addition, it prevents you from changing the
value of a
const.
The reason to declare an object of a built-in type as
const
is so the compiler will insure that it isn't changed.
You
can also tell the compiler that an object of a user-defined type is a
const.
Although it is conceptually possible that the compiler could store such an
object in its symbol table and generate compile-time calls to member functions
(so all the values associated with a
const
object would be available at compile-time), in practice it isn't feasible.
However, the other aspect of a
const
– that it cannot be changed during its lifetime – is still valid
and can be enforced.
const
member functions
The
compiler can tell when you're trying to change a simple variable, and can
generate an error. The concept of constness can also be applied to an object of
a user-defined type, and it meanst the same thing: the internal state of a
const
object cannot be changes. This can only be enforced if there is some way to
insure that all operations performed on a
const
object won't it. C++ provides a special syntax to tell the compiler that a
member function doesn't change an object.
The
keyword
const
placed after the argument list of a member function tells the compiler that
this member function can only read data members, but it cannot write them.
Creating a
const
member function is actually a contract which the compiler enforces. If you
declare a member function like this:
class X {
int i;
public:
int f() const;
};
then
f( )
can be called for any
const
object, and the compiler knows that it's a safe thing to do because you've said
the function is
const.
However, the compiler also insures that the function definition conforms to the
const
specifier. Not only are you forced to use the
const
specifier when you define the function (otherwise the compiler won't recognize
that the function is a member), but you cannot change any data members inside
the function or the compiler will generate an error:
int
X::f() const { return i++; } // Compiler reports error
So
the
const
function can be used on a
const
object because the declaration claims it is safe, and the compiler insures that
the definition conforms to this claim. Notice that the
const
keyword must be used in both the declaration and the definition. This is
illustrated in the following program:
//: C03:Constf.cpp
// const member functions
// Compiler checks for proper use of const
class CMembers {
int x;
public:
CMembers(int X) { x = X; }
int X() const { return x; }
int XX() { return x; } // Non-const, doesn't modify
//! int incr() const { return ++x; } // Error
void g() const; // Non-inline
};
void CMembers::g() const {
//! x++; // Error
}
int main() {
CMembers A(1);
const CMembers B(2);
A.X(); // Can call any member function for non-const objects
A.XX();
//! A.incr();
A.g();
B.X(); // Can only call const members for const objects
//! B.incr();
B.g();
//! B.XX(); // Error
} ///:~
Notice
that even though
XX( )
doesn't actually modify any data members, it hasn't been defined as a
const
member function so it can't be used with B. Also, the compiler will verify that
a function doesn't modify any data members if you say it's a
const,
whether or not that function is defined inline.
const
member functions can be called for non-
const
objects, but non-
const
functions cannot be called for
const
objects. Therefore, for the greatest flexibility of your classes, you should
declare functions as
const
when possible, since you never know when the user might want to call such a
function for a
const
object. This practice will be followed in this book.
Casting
away const-ness
In
some rare cases you may wish to modify certain members of an object, even if
the object is a
const.
That is, you may want to leave the
const
on all the members except a select few. You can do this with a rather
odd-looking cast. Remember that a cast tells the compiler to suspend its normal
assumptions and to let you take over the type-checking. Thus it is inherently
dangerous and not something you want to do casually. However, the need
sometimes occurs.
To
cast away the
const-ness
of an object, you select a member with the this pointer. Since this is just the
address of the current object, this seems redundant. However, by preceeding
this with a cast to itself, you implicitly remove the
const
(because
const
isn't part of the cast). Here's an example:
//: C03:Castaway.cpp
// Casting away the const-ness of an object
class FishingPole {
int rod, reel;
public:
FishingPole() { rod = reel = 0; }
void cast_away() const {
(((FishingPole*)this)->reel)++;
}
};
int main() {
const FishingPole fp;
fp.cast_away();
} ///:~
Inside
cast_
away( ),
the cast of this to type FishingPole (without the
const)
removes the
const-ness
of reel, while leaving rod as a
const.
Of course, this isn't the most straightforward code in the world, but when you
do this kind of thing you're intentionally breaking the type-safety mechanism,
and that is usually an ugly process. You should know what it looks like, but
you should try not to do it.
volatile
objects and member functions
A
volatile object is one which may be changed by forces outside the program's
control. For example, in a data communication program or alarm system, some
piece of hardware may cause a change to a variable, while the program itself
may never change the variable. The reason it's important to be able to declare
a variable volatile is to prevent the compiler from making assumptions about
code associated with that variable. The primary concern here is optimizations.
If you read a variable, and (without changing it) read it again sometime later,
the optimizer may assume that the variable hasn't changed and delete the second
read. If the variable was declared volatile, however, the optimizer won't touch
any code associated with the variable.
The
syntax for
const
and volatile member functions is identical. Only volatile member functions may
be called for volatile objects. In addition, objects and member functions can
be both
const
and volatile, as shown here:
//: C03:Constvol.cpp
// Const AND volatile together
class Comm {
unsigned char databyte;
public:
Comm() { databyte = 0; }
unsigned char read() const volatile {
return databyte;
}
};
int main() {
const volatile Comm port;
port.read();
} ///:~
databyte
is ostensibly where the data is placed (by hardware, or perhaps an interrupt
control routine). Since port is both
const
and volatile, it can only be read and the compiler won't optimize away any
reads of that location.
By
the previous logic, you should declare as many functions as possible both
const
and volatile so they would always be usable with objects which are
const
and/or volatile. In practice, however, volatile is used far less frequently than
const.
Go to CodeGuru.com
Contact: webmaster@codeguru.com
© Copyright 1997-1999 CodeGuru