cppannotations/annotations/yo/first/const.yo
Frank B. Brokken a018003af9 typo
2015-09-05 20:37:17 +02:00

172 lines
7 KiB
Text

Even though the keyword ti(const) is part of the bf(C) grammar, its use is
more important and much more common and strictly used in bf(C++) than it is in
bf(C).
The tt(const) keyword is a modifier stating that the value of a variable
or of an argument may not be modified. In the following example the intent is
to change the value of a variable tt(ival), which fails:
verb(
int main()
{
int const ival = 3; // a constant int
// initialized to 3
ival = 4; // assignment produces
// an error message
}
)
This example shows how tt(ival) may be initialized to a given value in its
definition; attempts to change the value later (in an assignment) are not
permitted.
Variables that are declared tt(const) can, in contrast to bf(C), be used to
specify the size of an array, as in the following example:
verb(
int const size = 20;
char buf[size]; // 20 chars big
)
Another use of the keyword tt(const) is seen in the declaration of
pointers, e.g., in pointer-arguments. In the declaration
verb(
char const *buf;
)
tt(buf) is a pointer variable pointing to tt(char)s. Whatever is pointed
to by tt(buf) may not be changed through tt(buf): the tt(char)s are declared
as tt(const). The pointer tt(buf) itself however may be changed. A statement
like tt(*buf = 'a';) is therefore not allowed, while tt(++buf) is.
In the declaration
verb(
char *const buf;
)
tt(buf) itself is a tt(const) pointer which may not be changed. Whatever
tt(char)s are pointed to by tt(buf) may be changed at will.
Finally, the declaration
verb(
char const *const buf;
)
is also possible; here, neither the pointer nor what it points to may be
changed.
The i(rule of thumb) for the placement of the keyword tt(const) is the
following: whatever occurs to the em(left) to the keyword may not be changed.
Although simple, this rule of thumb is often used. For example,
hi(Stroustrup)
Bjarne Stroustrup states (in
hi(http://www.research.att.com/...)
tlurl(http://www.research.att.com/~bs/bs_faq2.html#constplacement)):
quote(
em(Should I put "const" before or after the type?)
em(I put it before, but that's a matter of taste. "const T" and "T const"
were always (both) allowed and equivalent. For example:)
verb(
const int a = 1; // OK
int const b = 2; // also OK
)
em(My guess is that using the first version will confuse fewer programmers
(``is more idiomatic'').)
)
But we've already seen an example where applying this simple `before'
placement rule for the keyword tt(const) produces unexpected (i.e., unwanted)
results as we will shortly see (below). Furthermore, the `idiomatic'
before-placement also conflicts with the notion of emi(const functions), which
we will encounter in section ref(ConstFunctions). With const functions the
keyword tt(const) is also placed behind rather than before the name of the
function.
The definition or declaration (either or not containing tt(const)) should
always be read from the variable or function identifier back to the type
indentifier:
quote(
``Buf is a const pointer to const characters''
)
This rule of thumb is especially useful in cases where confusion may
occur. In examples of bf(C++) code published in other places one often
encounters the reverse: tt(const) em(preceding) what should not be
altered. That this may result in sloppy code is indicated by our second
example above:
verb(
char const *buf;
)
What must remain constant here? According to the sloppy interpretation,
the pointer cannot be altered (as tt(const) precedes the pointer). In fact,
the char values are the constant entities here, as becomes clear when we try
to compile the following program:
verb(
int main()
{
char const *buf = "hello";
++buf; // accepted by the compiler
*buf = 'u'; // rejected by the compiler
}
)
Compilation fails on the statement tt(*buf = 'u';) and em(not) on the
statement tt(++buf).
hi(Cline)
hi(http://www/parashift.com/c++-faq-lite/)
i(Marshall Cline)'s
turl(C++ FAQ)
(http://www.parashift.com/c++-faq-lite/const-correctness.html) gives the
same rule (paragraph 18.5) , in a similar context:
quote(em(
[18.5] What's the difference between "const Fred* p", "Fred* const p" and
"const Fred* const p"?)
em(You have to read pointer declarations right-to-left.)
)
Marshall Cline's advice can be improved, though. Here's a recipe that will
effortlessly dissect even the most complex declaration:
enumeration(
eit() start reading at the variable's name
eit() read as far as possible until you reach the end of the declaration or
an (as yet unmatched) closing parenthesis.
eit() return to the point where you started reading, and read backwards
until you reach the beginning of the declaration or a matching opening
parenthesis.
eit() If you reached an opening parenthese, continue at step 2 beyond the
parenthesis where you previously stopped.
)
Let's apply this recipe to the following (by itself irrelevant) complex
declaration. Little arrows indicate how far we should read at each step and
the direction of the arrow indicates the reading direction:
verb(
char const *(* const (*(*ip)())[])[]
ip Start at the variable's name:
'ip' is
ip+CHAR(41) Hitting a closing paren: revert
-->
(*ip) Find the matching open paren:
<- 'a pointer to'
(*ip)()CHAR(41) The next unmatched closing par:
--> 'a function (not expecting
arguments)'
(*(*ip)()) Find the matching open paren:
<- 'returning a pointer to'
(*(*ip)())[]CHAR(41) The next closing par:
--> 'an array of'
(* const (*(*ip)())[]) Find the matching open paren:
<-------- 'const pointers to'
(* const (*(*ip)())[])[] Read until the end:
-> 'an array of'
char const *(* const (*(*ip)())[])[] Read backwards what's left:
<----------- 'pointers to const chars'
)
Collecting all the parts, we get for tt(char const *(* const
(*(*ip)())[])[]): em(ip is a pointer to a function (not expecting arguments),
returning a pointer to an array of const pointers to an array of pointers to
const chars). This is what tt(ip) represents; the recipe can be used to parse
any declaration you ever encounter.