mirror of
https://gitlab.com/fbb-git/cppannotations
synced 2024-11-16 07:48:44 +01:00
172 lines
7 KiB
Text
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.
|