C++ Pitfall: const objects do not behave constantly.

J

James Kanze

On 2007-09-26 06:32, Kira Yamato wrote:
No, one again you confuse what the state of the object is. The state of
the HasOther object is that it knows of another object of type
OtherObject. Changing the state of the OtherObject does not affect the
state of the HasOther object, just the state of OtherObject.

It depends. A long time ago (around 15 years), there were great
debates over logical const vs. bitwise const: I once said
something along the lines of "so const really means whatever the
programmer wants it to mean", to which Andy Koenig responded
quite simply "yes", and another poster (Jim Adcock?) coined the
name of "Humpty-Dumpty const" (read Lewis Carrol's _Through_
_the_ _Looking_ _Glass_, if you don't get the reference).
Today, however, and for quite some time now, the consensus seems
to be logical const. In the case in question, if the
OtherObject is considered logically part of the state of
HasOther, then const functions of HasOther should not modify it.
(And if the name Has... means anything, it is.) Thus, for
example, most string classes will have a char* somewhere,
pointing to where the actual text resides; one expects const
functions of the class not to modify the actual text, however.
 
J

James Kanze

[...]
What is the state of an object?

That is, of course, the crux of the problem. As far as the
compiler is concerned, it is the bits that make up the object
itself, i.e. that are counted in sizeof. Most programmers
expect more, however; in the end, the state of an object is what
the author of the class documents as its state.
A state of an object is supposed to be a set of data that
determines the behavior of an object.

Not quite. Would you consider the machine connected at the
other end as part of the state of a Connection object? It's not
even part of the program.
In another word, knowing the state of an object would dictate
what its functions do given some inputs.

They do, within defined (and in well written code, documented)
limits. A print function, in an object, might output a time
stamp (which will vary each time it is called, and is
independant of the state of the object), if that's what it is
documented to do. For that matter, an object could be defined
to do one thing if the number of seconds since the epoch is odd,
and another if it is even. (Whether such an object has any
practical use is another question, of course.)
Otherwise, what is the point of knowing an object's state?

It constraints the behavior.
So, if you say that declaring an object 'const' should keep
its state constant, then its member functions should yield the
same output for the same input since the object remains in the
same state!

That's an enormous leap, and I don't know where you get that
idea from.
However, this is not the case as shown in the example.
This is the pitfall.
Rigorously, all 'const' really does is that it is a switch to disable
the invocation of certain member functions --- those that are not
declared 'const'.

Const means whatever the programmer wants it to mean. Yes.

In the end, classes are really about contracts. The author of
the class defines its contract. If he's a good programmer, it
will be a reasonable and useful contract, and const will have
some sort of relatively reasonable and intuitive meaning. It
won't mean (necessarily) that every invocation of a const
function will return the same value.

And of course, a bad programmer could abuse const (and mutable)
so that only the const functions modified the state, and the
non-const ones didn't. The language allows it, but such a
programmer won't last long in any place I've worked. The
language doesn't impose good design; I don't think a language
can, since it works at a much lower level. (The lack of any
errors in your English doesn't mean that you write at the same
level as Shakespeare does, either.)
 
J

James Kanze

On 2007-09-26 06:27, Kira Yamato wrote:

[...]
I can actually not see any difference between my example and
the one you gave, I have just moved the source of the
randomness from inside the function to outside. When it comes
to the object's state they are equal (in that the function's
results do not depend on the state of the object).

There's actually a very important difference, and Kira is
talking about an important concept (although it's not called
const, nor even constant): a pure function is one whose result
is entirely determined by its arguments (and the object a member
function is called on counts as an argument), and which modifies
no state (anywhere). There is no real support for pure
functions in C++. All a const function guarantees is that it
modifies no state of the object it is called on, and in
practice, what the state of the object consists of is defined by
the author of the class.

Your function is pure, his original function wasn't.
 
P

Philip Potter

James said:
It depends. A long time ago (around 15 years), there were great
debates over logical const vs. bitwise const: I once said
something along the lines of "so const really means whatever the
programmer wants it to mean", to which Andy Koenig responded
quite simply "yes", and another poster (Jim Adcock?) coined the
name of "Humpty-Dumpty const" (read Lewis Carrol's _Through_
_the_ _Looking_ _Glass_, if you don't get the reference).
Today, however, and for quite some time now, the consensus seems
to be logical const. In the case in question, if the
OtherObject is considered logically part of the state of
HasOther, then const functions of HasOther should not modify it.
(And if the name Has... means anything, it is.) Thus, for
example, most string classes will have a char* somewhere,
pointing to where the actual text resides; one expects const
functions of the class not to modify the actual text, however.

This is indeed a sensible (probably *the* sensible) way of using const. However,
as the OP has found out, the language does not /require/ you to use const in
this way.

There are a number of good reasons for this: pointers and references are used
for both has-a relationships (a car has an engine, which is part of the car) and
is-related-to relationships (a car is related to its owner, but the owner is not
part of the car), so some pointers and references will be fair game for const
methods while others will not. The language's job is not to specify how you
shall use its features.

It is possible to use "const" to mean many different things, especially with
gratuitous use of "mutable". However, James Kanze's description of "logical
const" is the least surprising one - among other things, it means you don't have
to know about a string class's internal structure to know that its const methods
won't modify its contents.

Just because a programming language feature can be used badly doesn't make the
feature bad.
 
B

Ben Rudiak-Gould

Victor said:
Here is another pitfall for you:

class HasOther {
OtherObject *ptr;
public:
HasOther(OtherObject *p) : ptr(new OtherObject) {}
void Gotcha(OtherObject const& value) const
{
if (ptr) *ptr = value;
}
};

Well, what about this:

class Foo {
public:
Foo* that;
int other;
Foo() : that(this), other(1) {}
void Gotcha() const { that->other = 2; }
};

const Foo foo;

foo.Gotcha();

There we have an object that's actually const (not merely accessed through a
const pointer) modifying its own state (not merely someone else's state),
without a const_cast or mutable qualifier in sight.

-- Ben
 
G

Guest

Well, what about this:

class Foo {
public:
Foo* that;
int other;
Foo() : that(this), other(1) {}
void Gotcha() const { that->other = 2; }
};

const Foo foo;

foo.Gotcha();

There we have an object that's actually const (not merely accessed through a
const pointer) modifying its own state (not merely someone else's state),
without a const_cast or mutable qualifier in sight.

If that is what you want you could just as well have used const_cast
instead, to make it more obvious to the reader what you were doing. Or
use mutable.

C++ is a very powerful language where you can access memory directly,
and there is nothing that prevents you from modifying that memory
(unless it is mapped in read only by the OS), so there is always a way
to get around whatever security the language provides.

"In C++ it's harder to shoot yourself in the foot, but when you do, you
blow off your whole leg."
— Bjarne Stroustrup.
 
K

Kira Yamato

If that is what you want you could just as well have used const_cast
instead, to make it more obvious to the reader what you were doing. Or
use mutable.

C++ is a very powerful language where you can access memory directly,
and there is nothing that prevents you from modifying that memory
(unless it is mapped in read only by the OS), so there is always a way
to get around whatever security the language provides.

Yes, it is because of this possible abuse that it is incorrect to
assume that 'const' keeps an object's state constant. In the example
above, it does not even do bitwise-const, much less logical-const.

This is the pitfall.

As someone has pointed out before, it is not 'const' that keeps
object's state constant, but rather, it is the programmers who write
correct codes that keep the object's state constant.
 
J

James Kanze

Well, what about this:
class Foo {
public:
Foo* that;
int other;
Foo() : that(this), other(1) {}
void Gotcha() const { that->other = 2; }
};
const Foo foo;

There we have an object that's actually const (not merely
accessed through a const pointer) modifying its own state (not
merely someone else's state), without a const_cast or mutable
qualifier in sight.

That's undefined behavior. On the other hand, if int were
declared mutable, there'd be no problem.

More generally, the one place const does cause confusion is the
fact that it only inhibits changes through the particular lvalue
that is declared const; it doesn't guarantee in any way that the
object will not change state. For example:

Foo globalFoo ;

void
f( Foo const& foo )
{
std::cout << foo.state() << std::endl ;
globalFoo.changeState() ;
std::cout << foo.state() << std::endl ;
}

may output two different things if f is called with globalFoo as
an argument.
 
J

James Kanze

C++ is a very powerful language where you can access memory
directly, and there is nothing that prevents you from
modifying that memory (unless it is mapped in read only by the
OS), so there is always a way to get around whatever security
the language provides.

If an object itself is defined const, the compiler is allowed to
put it into read-only memory (unless it contains a mutable
member). Modifying, or attempting to modify, a const object, is
undefined behavior.
"In C++ it's harder to shoot yourself in the foot, but when
you do, you blow off your whole leg."
? Bjarne Stroustrup.

Now that's more relevant to the spaghetti code thread, where C++
not only provides goto, but you have the possibility of
spaghetti inheritance and spaghetti templates as well.
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,274
Messages
2,571,368
Members
48,060
Latest member
JerrodSimc

Latest Threads

Top