Topic: const object with constructors
Author: kanze@us-es.sel.de (James Kanze (R.30)
Date: Wed, 10 Jun 92 14:07:01 GMT Raw View
I have just finished reading in "C++ Programming Guidelines", by
Thomas Plum and Dan Saks, that it is legal to modify an object
declared "const" if this object has a constructor (in Topic 1.11).
This seems wrong to me.
I verified in the ARM; all I found there was: "A const object of a
type that does not have a constructor or a destructor may be placed
in readonly memory." This would seem to imply that a const object with
either a constructor or a destructor may *not* be placed in readonly
memory.
My understanding of a const was that the object could not be modified
after initialization.
Some questions:
1. Plum and Saks state that a const object with a constructor *may*
be legally modified by a non-const member function. To do this, the
function must cast the this pointer to non-const. Isn't this undefined?
If not, why not? What is the official position of the ANSI standards
committee on this.
2. Is it legal to make the memory of a const object with constructors
readonly *after* the constructor has executed? I don't know of any
hardware which will support this at present, but supposing the hardware
would allow it, why forbid it. (I suppose that you would again have
to render the memory writable for the destructor.)
A somewhat similar problem: if the constructor is inline, contains no
side-effects, and the compiler can figure out what the object would
look like *after* the constructor has executed, is it legal to simple
initialize the memory correctly, without executing a subroutine. For
example:
class complex
{
double r ;
double i ;
public :
complex (double real = 0.0 , double imag = 0.0)
{ r = real ; i = imag ; }
// ...
} ;
given the definition:
static complex z (3.0,4.0) ;
Would it be legal for a compiler to simply generate initialization
records for z. If z were also declared const, would it be legal for the
compiler to simply place the two double's directly in readonly memory?
If not, why not?
IMHO, the above is legal. Even more, given a member function of complex:
inline double
complex::magnitude ()
{
return r*r + i*i ;
}
I would expect that, given the following:
const complex z (3.0 , 4.0) ;
f ()
{
double x ;
x = z.magnitude () ;
}
the compiler should simply generate a move immediate of 25.0 into x.
(This is a simple transformation, commonly called constant folding,
which is currently done by almost all C compilers.)
Anyone care to comment...
--
James Kanze email: kanze@us-ees.sel.de
GABI Software, Sarl
8 rue du Faisan
67000 Strasbourg
France
Author: jss@lucid.com (Jerry Schwarz)
Date: Thu, 11 Jun 92 18:06:56 GMT Raw View
In article <1992Jun10.140701.2317@us-es.sel.de>, kanze@us-es.sel.de (James Kanze (R.30) writes:
|>
|> I have just finished reading in "C++ Programming Guidelines", by
|> Thomas Plum and Dan Saks, that it is legal to modify an object
|> declared "const" if this object has a constructor (in Topic 1.11).
|>
|> This seems wrong to me.
|>
|> I verified in the ARM; all I found there was: "A const object of a
|> type that does not have a constructor or a destructor may be placed
|> in readonly memory." This would seem to imply that a const object with
|> either a constructor or a destructor may *not* be placed in readonly
|> memory.
|>
|> My understanding of a const was that the object could not be modified
|> after initialization.
Plum and Saks are correct.
Another paragraph in the ARM is relevant. This is section 5.4
bottom of page 70.
A pointer to an object of a const type can be cast into a
pointer to a non-const type. The resulting pointer will
refer to the original object. ... The result of attempting
to modify that object through such a pointer or reference
will either cause an addressing exception or be the same as if the
original pointer ... had referred [to] a non-const object.
It is implementation dependent whether the addressing exception
occurs.
Although the paragraph isn't explicit I believe it is intended
to refer to "stripping of a const" (conversion from "const T*" to
"T*"), not just any conversion from const to non-const.
The last two sentences of the above paragraph have been changed in
X3J16's current working paper to
Depending on the type of the referenced object, a write
operation through the resulting pointer or reference may
be undefined; see 7.1.6.
7.1.6 is the section containing the paragraph of the original article.
The purpose of this change is to get rid of the specific wording
that required an "addressing exception" rather than undefined behavior.
And to be explicit about some cases where stripping of const is
allowed.
The purpose of these provisions is to allow classes to implement
"abstract constness".
As an example of "abstract constness" suppose I have a class, Map,
that represents maps from type A to type B. The class has functions
to look up values of A and to modify the map. The interface would
allow only lookup functions to be applied to a "const Map".
In this circumstance, I may want the lookup function to
modify the underlying (private) data structures in a Map.
For example if the Map is implemented as some kind of balanced tree
a lookup operation may cause rebalancing.
X3J16 is considering at least one proposal that would contain explicit
language support for abstract constness. If it is accepted then I expect
the provision that allows modification of const objects would
also be modified (or eliminated).
-- Jerry Schwarz
There is a least one proposal being considered by X3J16 for a language
extension that would
Author: jimad@microsoft.com (Jim Adcock)
Date: 13 Jun 92 00:45:31 GMT Raw View
In article <1992Jun11.180656.5842@lucid.com> jss@lucid.com (Jerry Schwarz) writes:
|The purpose of these provisions is to allow classes to implement
|"abstract constness".
Note, however, it is not the *classes* that are implementing "abstract
constness" but programmers, if that's what programmers choose to do.
If programmers choose to cast-away constness willy-nilly, they can
do that too -- whether or not that's the "intent" of the committee:
complex imag_pi = (0, 3.14159);
....
if (imag_part(imag_pi) != 3.14159) // well, I guess someone cast away
FortranLikeFU(); // your const. tough luck buddy!
Unfortunately, good intentions sometimes lead to bad decisions.
Consider for example the case of noalias.
If programmers want to implement classes with "abstract constness" there's
lots of safe" ways for them to do that without messing up the definition
of const!
Author: maxtal@extro.ucc.su.OZ.AU (John (MAX) Skaller)
Date: Sat, 13 Jun 1992 09:39:51 GMT Raw View
In article <1992Jun10.140701.2317@us-es.sel.de> kanze@us-es.sel.de (James Kanze (R.30)) writes:
>
>2. Is it legal to make the memory of a const object with constructors
>readonly *after* the constructor has executed? I don't know of any
>hardware which will support this at present, but supposing the hardware
>would allow it, why forbid it. (I suppose that you would again have
>to render the memory writable for the destructor.)
>
The Intel 80386 can do this. In fact, with compiler
support one could have two segements aliased togther, one
read-only and the other read-write. The constructor for
a const-object would access the read-write segment
to build the object, all other accesses (up to destruction)
would be to the read-only segment.
C++ might have some trouble with this though, since
two distinct pointers would point to the same object:
one to the read-only object and the other to the read-write
object.
--
;----------------------------------------------------------------------
JOHN (MAX) SKALLER, maxtal@extro.ucc.su.oz.au
Maxtal Pty Ltd, 6 MacKay St ASHFIELD, NSW 2131, AUSTRALIA
;--------------- SCIENTIFIC AND ENGINEERING SOFTWARE ------------------
Author: jimad@microsoft.com (Jim Adcock)
Date: 16 Jun 92 18:26:26 GMT Raw View
In article <1992Jun13.093951.9281@ucc.su.OZ.AU> maxtal@extro.ucc.su.OZ.AU (John (MAX) Skaller) writes:
|In article <1992Jun10.140701.2317@us-es.sel.de> kanze@us-es.sel.de (James Kanze (R.30)) writes:
|>
|>2. Is it legal to make the memory of a const object with constructors
|>readonly *after* the constructor has executed?
No, because the ANSI-C++ committee decreed [is in the processes of decreeing]
that it is not legal to do so. On the contrary, implementors have to remain
open to the possibility that some hacker may want to cast-away the constness
later and twiddle the bits. Can you say: "encapsulation" ???
This is analogous [to me at least] of FORTRAN where you can *declare* something
const and then one of your good buddies writes a routine that throws-away const
because "he knows what he's doing" and all of a sudden your const isn't.
FORTRAN experience is that these are very difficult bugs to track down, so
I think it is a mistake that the ANSI-C++ is re-inventing this approach what
30+ years later? The intentions are good, but the result is bad -- just as in
the case of "noalias."
|>I don't know of any
|>hardware which will support this at present, but supposing the hardware
|>would allow it, why forbid it.
Because if you forbid it, that would require some hackers to put the
"volatile" keyword on some members of some classes who have some objects
that are going to be declared "const." Or they would have had to use
an extra level of indirection on that member. This is "clearly" too
onerous for such hackers so instead we're going to generate bigger fatter
slower unsafe code for everyone.
|>(I suppose that you would again have
|>to render the memory writable for the destructor.)
Also, an implementation might [have been] smart enough to be able to create
some of this stuff in "discardable" memory and reconstruct it as needed.
[discardable memory is memory whose contents are "destroyed" instead of
being paged out. When the memory needs to be "paged-in" again, the
objects in that memory are simply reconstructed. If construction doesn't
have side-effects, then doing such is "no problem"]
| The Intel 80386 can do this. In fact, with compiler
|support one could have two segements aliased togther, one
|read-only and the other read-write. The constructor for
|a const-object would access the read-write segment
|to build the object, all other accesses (up to destruction)
|would be to the read-only segment.
|
| C++ might have some trouble with this though, since
|two distinct pointers would point to the same object:
|one to the read-only object and the other to the read-write
|object.
This depends on exactly how one defines the [what imho ought to be illegal]
act of casting-away constness of a const object. One *could* say that the const
address of a const object need not be the same as the cast-away-const address
of that object. In which case the above read-only and read-write addresses
would not have to be the same. Alternately, the compiler could be [a lot]
smarter when it came to comparing const pointers to non-const pointers,
doing the "appropriate" selector mappings as required.
Author: jss@lucid.com (Jerry Schwarz)
Date: Wed, 17 Jun 92 01:37:34 GMT Raw View
|jss> |The purpose of these provisions is to allow classes to implement
|jss> |"abstract constness".
|jimad> Note, however, it is not the *classes* that are implementing "abstract
|jimad> constness" but programmers, if that's what programmers choose to do.
|jimad> If programmers choose to cast-away constness willy-nilly, they can
|jimad> do that too -- whether or not that's the "intent" of the committee:
True, but I don't see the relevance. What counts in the public
interface is what the programmer intends to say about the class
class IntSet {
public:
int IsMember(int n) const ; /* True if n is a member of the set */
int Add(int n) ; /* Add n to the set */
...
private:
int* p ;
} ;
"const" is used (by whoever designed the IntSet interface) to
indicate that IsMember doesn't change the represented set, while Add
does. So, for example,
void think(const& IntSet) ;
...
const IntSet s = ... ;
...
think(s) ; /* s is not modified by this call to think */
All this goes on at the "abstract level" of sets of ints.
When I come to implement "IsMember" I have to be careful
that I don't change the represented set. I could make
a mistake without casting away const. For example I might
modify the value that p points at. Such a mistake would
be a bug. According to this line of thinking there is
no reason to prohibit modification of p in IsMember. It is
is the programmers responsibility to guarantee that any modification
of p doesn't change the represented set, just as it is the programmers
responsibility to guarantee that any modification of what p points
at doesn't change the set either.
The above is not intended as a justification for "casting away const"
but as an explanation of the notion of "abstract constness".
Many members of X3J16 feel that this notion should be supported
to the extent that some method should be available for IsMember to
modify p.
I do not deny that the mechanism currently in place (casting away
const) has drawbacks. In looking at it I try to balance the advantages
of supporting abstract constness against the uglyness and loss of
type safety of casting away const. At the moment [I've changed
my mind in the past] I come down in favor of allowing casting away const,
but for me it is a close call.
-- Jerry Schwarz
Author: jimad@microsoft.com (Jim Adcock)
Date: 17 Jun 92 23:23:22 GMT Raw View
In article <1992Jun17.013734.1312@lucid.com> jss@lucid.com (Jerry Schwarz) writes:
||jimad> Note, however, it is not the *classes* that are implementing "abstract
||jimad> constness" but programmers, if that's what programmers choose to do.
||jimad> If programmers choose to cast-away constness willy-nilly, they can
||jimad> do that too -- whether or not that's the "intent" of the committee:
|
|True, but I don't see the relevance. What counts in the public
|interface is what the programmer intends to say about the class
...
If this is the intent of the committee then the committee should implement
of the intent of the committee and NOT what they ARE implementing -- which
is the ability of ANY programmer to cast away constness on ANY object ANY
time ANY place. Not just the case of a programmer casting away the constness
of an object of his class in a "abstract const" member function of that class.
*For Example*, instead of allowing ANY use of cast-from-const the committee
could instead make the much less damaging decree:
"In the sole case of a class whose public members are all member functions
then declaring a member function const does not prevent a programmer
from casting-away the constness of the this object of that member function,
thereby allowing modifications of the this object's members. Any other
use of const-from-const when the referenced object is a const object
represents an unconstrained error."
But, such is not what the committee is implementing. The committee is
allowing ANY use of cast-from-const on objects that have constructors or
destructors. NOT just the use of cast-from-const in implementing abstract
const class interfaces.
Further, note that cast-from-const is TOTALLY unnecessary to allow programmers
to implement abstract-const interfaces -- programmers can and do implement
abstract-const interfaces without making any use of cast-from-const hackery.
Alternative less-hack approaches to solving this problem include adding
a level of indirection [a const pointer to a non-const object] declaring
the member volatile, or what have you.
So the only possible justification for cast-from-const is a "utility"
argument -- the "convenience" factor in allowing hackers to use cast-from-const
to hack bits outweighs the importance of allowing compilers to diagnose
serious programming errors and allowing compilers to generate highly
optimized code. I disagree that the "utility" in allowing hackers
these hacks outweighs the resulting losses to serious C++ programmers.
-- Especially when many less-damaging alternatives are available to the
committee.
Author: kendall@centerline.com (Sam Kendall)
Date: Thu, 18 Jun 1992 00:54:03 GMT Raw View
To recap: kanze@us-es.sel.de (James Kanze (R.30) feels that const
objects with constructors should not be modifiable after construction.
The current ANSI draft (92-0023), on the other hand, has a notion of
"ROMable" objects, which can be placed in read-only memory; all other
objects, including const objects with constructors, are explicitly in
readable-writable memory. The ANSI committee is considering a notion of
"abstract const"; this would enshrine allowing const member functions to
modify their private data members.
Abstract const sounds like it might be a good idea. But suppose it
is rejected, and we stick with "concrete const". Should the language
semantics then be made to conform to the usual hardware model, as found
in the "ROMable" concept?
No, because even if most chips can't easily support making const
objects read-only after construction, special execution environments
can. For example, ObjectCenter's (my company's C++ programming
environment) runtime checking could be enhanced to make an object
read-only after its constructor returns, so that post-construction
writes to the object would cause a warning. The reverse would happen
when the destructor was invoked; the storage would be made writable
again. It sounds like C++ environments on Intel machines could use the
hardware to achieve a similar effect.
My general point is that there are execution environments for
debugging and testing that are richer, and allow better consistency
checking, than normal "stripped-down" execution environments. For
another example, see "Adding Run-time Checking to the Portable C
Compiler" in the April 1992 issue of Software--Practice and Experience.
----
Sam Kendall
CenterLine Software, Inc. (formerly Saber Software)
Author: pabloh@hpwalea.hpl.hp.com (Pablo Halpern )
Date: 19 Jun 92 16:26:44 GMT Raw View
In article <1992Jun11.180656.5842@lucid.com>, jss@lucid.com (Jerry
Schwarz) writes:
|>X3J16 is considering at least one proposal that would contain explicit
|>language support for abstract constness. If it is accepted then I expect
|>the provision that allows modification of const objects would
|>also be modified (or eliminated).
Let's hope so. On the other hand, I can think, off the top of my head of
several idioms for implementing abstract const-ness without changing
the language and without requiring cast-away-const. I want some comment
on this so that I can send it to the ANSI committee as an argument for
making cast-away-const an undefined (or severely restricted) operation.
For my example, I will use a class the implements an abstract interface
to a database. The database class caches the most recently accessed
records in the database. The client of this class can read (lookup)
records from the database or write (store) records to the database.
A "const" database object permits lookup but does not permit storing.
Because a lookup updates the cache, we have an instance of "abstract
const," where the data structure is modified although the abstract
interface says it is not.
Idiom 1:
// Database Class definition
class DB {
public:
DB(const char* filename);
DB_record lookup(const char* key) const;
void store(const DB_record& rec);
protected:
DB_record* cache;
...
};
// Database instances
DB normDB("myDataBase");
const DB constDB("myOtherDataBase");
This is the simplest idiom. Both the lookup() and store() functions
access the cache member, which points to a non-const (array of) record(s).
Nothing prevents lookup() from modifying this array, even though it is
declared as a const function. If the compiler puts the constDB instance
in read-only memory, everything works fine, provided that lookup()
does not try to modify any direct member variables. Any other instance
variables that need to be modified by lookup() can similarly be accessed
through indirection. The client need have no knowledge of the
"abstract const-ness" of this object.
Idiom 2:
// Database Class definition
class ConstDB {
public:
ConstDB(const char* filename);
ConstDB_record lookup(const char* key);
protected:
DB_record cache[CACHE_SIZE];
...
};
class DB : public ConstDB {
public:
DB(const char* filename) : ConstDB(filename) {}
void store(const DB_record& rec);
};
// Database instances
DB normDB("myDataBase");
ConstDB constDB("myOtherDataBase");
This idiom is somewhat messier that the previous one. Here, the
const-ness of the database object is determined not by the const
qualifier, but by the class the object is declared as. Because
no object is explicitly declared "const," the compiler will not
attempt to make it read-only. The interface is preserved such that
a DB object has lookup() and store() functions but a ConstDB object
has only a lookup() function
Idiom 3:
This may require simple language support:
// Database Class definition
class DB {
public:
DB(const char* filename);
DB_record lookup(const char* key) const;
void store(const DB_record& rec);
protected:
volatile DB_record cache[CACHE_SIZE];
...
};
// Database instances
DB normDB("myDataBase");
const DB constDB("myOtherDataBase");
The language could specify something to the effect that declaring an
instance variable "volatile" prevents an object from being stored
in read-only memory and gives "cast-away-const" well-defined
sematantics.
Comments and additional idioms, please!
- Pablo
------------------------------------------------------------------------
Pablo Halpern (617) 290-3542
HP Waltham pabloh@hpwarq.wal.hp.com
I am self-employed, so my opinions *do* reflect those of my employer.
However, they may not reflect the opinions of my client.
------------------------------------------------------------------------
Author: pkt@lpi.liant.com (Scott Turner)
Date: Sat, 20 Jun 1992 14:58:31 GMT Raw View
Sam Kendall writes:
> But suppose [...] we stick with "concrete const". Should the language
> semantics then be made to conform to the usual hardware model, as found
> in the "ROMable" concept?
>
> No, because even if most chips can't easily support making const
> objects read-only after construction, special execution environments
> can.
I believe you're saying that all declared-const objects should be
candidates for ROM, from just after construction (if present) to just
before destruction (if present). This was unclear in the
article, because you're asking to eliminate the "ROMable" distinction
but not to eliminate the ROMability of objects. Correct me if I'm wrong.
--
Prescott K. Turner, Jr.
Liant Software Corp. (developers of LPI language products)
959 Concord St., Framingham, MA 01701 USA (508) 872-8700
UUCP: ...uunet!lpi!pkt Internet: pkt@lpi.liant.com