2.19 Why do many functions come in pairs that
are nearly identical except for a const?
class
Point
{
int
xcoord
;
int
ycoord
;
public
:
Point
(
int
x
,
int
y
)
:
xcoord
(
x
),
yxoord
(
y
)
{}
int
&
x
()
{
return
xcoord
;}
int
&
y
()
{
return
ycoord
;}
};
Let’s suppose that we have a simple ADT: Note that, because
the x() and y() functions return a reference (an
address), we can both look at and assign to the components of a point:
aPoint
.
x
()
=
aPoint
.
y
();
void
foo
(
Point
&
p1
,
const
Point
&
p2
)
{
cout
<<
"
(
"
<<
p1
.
x
()
<<
"
,
"
<<
p1
.
y
()
<<
"
)
"
<<
endl
;
p1
.
x
()
=
14;
cout
<<
"
(
"
<<
p2
.
x
()
<<
"
,
"
<<
p2
.
y
()
<<
"
)
"
<<
endl
;
p2
.
x
()
=
14;
Now suppose that we try to use our ADT in a function. This code has
a real error in it. We have received p2 as a const reference. That means
that foo promises not to do
anything to p2 that would change
its value. The final assignment statment clearly attpents to change
p2. Obviously the programmer is
confused and has forgotten that promise. When we compile this
function, we would get error messages from the compiler on all our
uses of p2 (not just on the
assignment). Why? Because foo
promises not to do anything to p2 that would change its value, and
the compiler is going to enforce that promise. Obviously the
assignment of 14 to p2.x()
breaks that promise. But, in general, the compiler has no way of
knowing that any call to x() or y() would not change the Point they were applied to. The fact
that the current bodies of those don’t make any such changes
is irrelevant - the designer of the Point ADT could come along and change
those at any time. So the compiler will not permit us to apply
any function to p2 that does not promise to leave
p2 unchanged.
How does a member function promise to leave its
object unchanged? By having the word const after the parameter list.
So we could change our ADT as shown here, which
would allow our foo function to
compile.
class Point {
int xcoord;
int ycoord;
public:
Point (int x, int y) :
xcoord(x), yxoord(y)
{}
int& x() const {return xcoord;}
int& y() const {return ycoord;}
};
But we’re really lying to the compiler
here. We are saying that these two functions are safe to use with a
value we don’t want changed. The simple fact that we can
usethem to write
shows this to be a lie.
With this change
class Point {
int xcoord;
int ycoord;
public:
Point (int x, int y) :
xcoord(x), yxoord(y)
{}
int x() const {return xcoord;}
int y() const {return ycoord;}
};
We can live up to our promise by changing the output type:but now
when we compile
void
foo
(
Point
&
p1
,
const
Point
&
p2
)
{
cout
<<
"
(
"
<<
p1
.
x
()
<<
"
,
"
<<
p1
.
y
()
<<
"
)
"
<<
endl
;
p1
.
x
()
=
14;
cout
<<
"
(
"
<<
p2
.
x
()
<<
"
,
"
<<
p2
.
y
()
<<
"
)
"
<<
endl
;
p2
.
x
()
=
14;
both assignments get flagged with errors. That’s OK for the
assignment to p2, which we
should not be allowed to do (since p2 is declared as const) but
there’s no logical reason to forbid this operation on the
non-const p1.
We get the best of both worlds be including both
the const and the original non-const forms of these functions:
class Point {
int xcoord;
int ycoord;
public:
Point (int x, int y) :
xcoord(x), yxoord(y)
{}
int& x() {return xcoord;}
int x() const {return xcoord;}
int& y() {return ycoord;}
const int& y() const {return ycoord;}
};
Now when we compile
void
foo
(
Point
&
p1
,
const
Point
&
p2
)
{
cout
<<
"
(
"
<<
p1
.
x
()
<<
"
,
"
<<
p1
.
y
()
<<
"
)
"
<<
endl
;
p1
.
x
()
=
14;
cout
<<
"
(
"
<<
p2
.
x
()
<<
"
,
"
<<
p2
.
y
()
<<
"
)
"
<<
endl
;
p2
.
x
()
=
14;
the compiler chooses, for each call to x() and y() the “best fitting”
version of the function. When applied to the non-const p1, the compiler uses the non-const
versions of x() and y(). When applied to the const p2, the the compiler uses the const
versions of x() and y(). That menas that only the line
with assignment to p2gets
flagged, as is appropriate.