Don't hesitate to send in feedback: send an e-mail if you like the C++ Annotations; if you think that important material was omitted; if you find errors or typos in the text or the code examples; or if you just feel like e-mailing. Send your e-mail to Frank B. Brokken.Please state the document version you're referring to, as found in the title (in this document: 7.3.0) and please state chapter and paragraph name or number you're referring to.
All received mail is processed conscientiously, and received suggestions for improvements will usually have been processed by the time a new version of the Annotations is released. Except for the incidental case I will normally not acknowledge the receipt of suggestions for improvements. Please don't interpret this as me not appreciating your efforts.
In this chapter C++ is further explored. The possibility to
declare functions in struct
s is illustrated in various examples. The
concept of a class
is introduced.
::
). This operator can be
used in situations where a global variable exists having the same name as a
local variable:
#include <stdio.h> int counter = 50; // global variable int main() { for (register int counter = 1; // this refers to the counter < 10; // local variable counter++) { printf("%d\n", ::counter // global variable / // divided by counter); // local variable } return 0; }In this code fragment the scope operator is used to address a global variable instead of the local variable with the same name. In C++ the scope operator is used extensively, but it is seldom used to reach a global variable shadowed by an identically named local variable. Its main purpose will be described in chapter 6.
#include <iostream> using namespace std; int main() { int ival; char sval[30]; cout << "Enter a number:" << endl; cin >> ival; cout << "And now a string:" << endl; cin >> sval; cout << "The number is: " << ival << endl << "And the string is: " << sval << endl; }This program reads a number and a string from the
cin
stream (usually
the keyboard) and prints these data to cout
. With respect to streams,
please note:
iostream
. In
the examples in the Annotations this header file is often not mentioned
explicitly. Nonetheless, it must be included (either directly or
indirectly) when these streams are used. Comparable to the use of the using
namespace std;
clause, the reader is expected to #include <iostream>
with
all the examples in which the standard streams are used.
cout
, cin
and cerr
are variables of so-called
class-types. Such variables are commonly called
objects. Classes
are discussed in detail in chapter 6 and are used extensively in
C++.
cin
extracts data from a stream and copies the
extracted information to variables (e.g., ival
in the above example) using
the extraction operator (two consecutive >
characters: >>). We will
describe later how operators in C++ can perform quite different actions
than what they are defined to do by the language, as is the case
here. Function overloading has already been mentioned. In C++
operators can also have multiple definitions, which is called operator
overloading.
cin
, cout
and cerr
(i.e.,
>> and <<) also manipulate variables of different types. In the
above example cout
<< ival
results in the printing of an integer
value, whereas cout
<< "Enter a number"
results in the printing
of a string. The actions of the operators therefore depend on the types of
supplied variables.
cout
is usually implemented by inserting the
endl
symbol, rather than the string "\n"
.
cin
, cout
and cerr
are not part of the
C++ grammar, as defined in the compiler which parses source files. The
streams are part of the definitions in the header file iostream
. This is
comparable to the fact that functions like printf()
are not part of the
C grammar, but were originally written by people who considered such
functions important and collected them in a run-time library.
Whether a program uses the old-style functions like printf()
and
scanf()
or whether it employs the new-style streams is a matter of taste.
The two styles can even be mixed. Some advantages are given below:
C
functions printf()
and
scanf()
, the usage of the insertion and extraction operators is more
type-safe. The format strings which are used with printf()
and
scanf()
can define wrong format specifiers for their arguments, for which
the compiler sometimes can't warn. In contrast, argument checking with
cin
, cout
and cerr
is performed by the compiler. Consequently it
isn't possible to err by providing an int
argument in places where,
according to the format string, a string argument should appear.
printf()
and scanf()
, and other functions which
use format strings, in fact implement a mini-language which is interpreted at
run-time. In contrast, the C++
compiler knows exactly which in- or
output action to perform given which argument.
C++
. Again, it
requires a little getting used to, ascending from C, but after that these
overloaded operators feel rather comfortable.
Iostreams
are extensible: new functionality can easily be
added to existing functionality, a phenomenon called
inheritance. Inheritance is discussed in detail in chapter
13.
cin, cout
and cerr
. In chapter 5 iostreams will be covered in
greater detail. Even though
printf()
and friends can still be used in
C++ programs, streams are practically replacing the old-style C
I/O
functions like printf()
. If you think you still need to use
printf()
and related functions, think again: in that case you've probably
not yet completely grasped the possibilities of stream objects.
const
is part of the C grammar, it is more
important and much more common in C++ than it is in C.
The const
keyword is a modifier which states 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 ival
, which fails:
int main() { int const ival = 3; // a constant int // initialized to 3 ival = 4; // assignment produces // an error message }This example shows how
ival
may be initialized to a given value in its
definition; attempts to change the value later (in an assignment) are not
permitted.
Variables which are declared const
can, in contrast to C, be used as
the specification of the size of an array, as in the following example:
int const size = 20; char buf[size]; // 20 chars bigAnother use of the keyword
const
is seen in the declaration of
pointers, e.g., in pointer-arguments. In the declaration
char const *buf;
buf
is a pointer variable, which points to char
s. Whatever is
pointed to by buf
may not be changed: the char
s are declared as
const
. The pointer buf
itself however may be changed. A statement like
*buf = 'a';
is therefore not allowed, while buf++
is.
In the declaration
char *const buf;
buf
itself is a const
pointer which may not be changed. Whatever
char
s are pointed to by buf
may be changed at will.
Finally, the declaration
char const *const buf;is also possible; here, neither the pointer nor what it points to may be changed.
The
rule of thumb for the placement of the keyword const
is the
following: whatever occurs to the left to the keyword may not be changed.
Although simple, this rule of thumb is not often used. For example, Bjarne Stroustrup states (in http://www.research.att.com/~bs/bs_faq2.html#constplacement):
Should I put "const" before or after the type?Below we'll see an example where applying this simple `before' placement rule for the keywordI put it before, but that's a matter of taste. "const T" and "T const" were always (both) allowed and equivalent. For example:
const int a = 1; // OK int const b = 2; // also OKMy guess is that using the first version will confuse fewer programmers (``is more idiomatic'').
const
produces unexpected (i.e., unwanted)
results. Apart from that, the `idiomatic' before-placement conflicts with the
notion of
const functions, which we will encounter in section
6.2, where the keyword const
is also written behind the
name of the function.
The definition or declaration in which const
is used should be read from
the variable or function identifier back to the type indentifier:
``Buf is a const pointer to const characters''This rule of thumb is especially useful in cases where confusion may occur. In examples of C++ code, one often encounters the reverse:
const
preceding what should not be altered. That this may result in
sloppy code is indicated by our second example above:
char const *buf;What must remain constant here? According to the sloppy interpretation, the pointer cannot be altered (since
const
precedes the pointer). In
fact, the charvalues are the constant entities here, as will be clear when we
try to compile the following program:
int main() { char const *buf = "hello"; buf++; // accepted by the compiler *buf = 'u'; // rejected by the compiler return 0; }Compilation fails on the statement
*buf = 'u';
, not on the
statement buf++
.
Marshall Cline's C++ FAQ gives the same rule (paragraph 18.5) , in a similar context:
[18.5] What's the difference between "const Fred* p", "Fred* const p" and "const Fred* const p"?Marshal Cline's advice might be improved, though: You should start to read pointer definitions (and declarations) at the variable name, reading as far as possible to the definition's end. Once you see a closing parenthesis, read backwards (right to left) from the initial point, until you find matching open-parenthesis or the very beginning of the definition. For example, consider the following complex declaration:You have to read pointer declarations right-to-left.
char const *(* const (*ip)[])[]Here, we see:
ip
, being a
int int_value; int &ref = int_value;In the above example a variable
int_value
is defined. Subsequently a
reference ref
is defined, which (due to its initialization) refers to the
same memory location as int_value
. In the definition of
ref
, the reference operator &
indicates that ref
is not
itself an integer but a reference to one. The two statements
int_value++; // alternative 1 ref++; // alternative 2have the same effect, as expected. At some memory location an
int
value is increased by one. Whether that location is called int_value
or
ref
does not matter.
References serve an important function in C++ as a means to pass
arguments which can be modified. E.g., in standard C, a function that
increases the value of its argument by five but returns nothing
(void
), needs a pointer parameter:
void increase(int *valp) // expects a pointer { // to an int *valp += 5; } int main() { int x; increase(&x) // the address of x is return 0; // passed as argument }This construction can also be used in C++ but the same effect can also be achieved using a reference:
void increase(int &valr) // expects a reference { // to an int valr += 5; } int main() { int x; increase(x); // a reference to x is return 0; // passed as argument }It can be argued whether code such as the above is clear: the statement
increase
(x)
in the main()
function suggests that not x
itself but a copy is passed. Yet the value of x
changes because of
the way increase()
is defined.
Actually, references are implemented using pointers. So, references in C++ are just pointers, as far as the compiler is concerned. However, the programmer does not need to know or to bother about levels of indirection. Nevertheless, pointers and references should be distinguished: once initialized, references can never refer to another variable, whereas the values of pointer variables can be changed, which will result in the pointer variable pointing to another location in memory. For example:
extern int *ip; extern int &ir; ip = 0; // reassigns ip, now a 0-pointer ir = 0; // ir unchanged, the int variable it refers to // is now 0.
In order to prevent confusion, we suggest you adhere to the following:
void some_func(int val) { cout << val << endl; } int main() { int x; some_func(x); // a copy is passed, so return 0; // x won't be changed }
void by_pointer(int *valp) { *valp += 5; }
void by_reference(string const &str) { cout << str; } int main () { int x = 7; string str("hello"); by_pointer(&x); // a pointer is passed by_reference(str); // str is not altered return 0; // x might be changed }References play an important role in cases where the argument will not be changed by the function, but where it is undesirable to use the argument to initialize the parameter. Such a situation occurs when a large variable, e.g., a
struct
, is passed as argument, or is returned by the function. In these
cases the copying operation tends to become a significant factor, as the
entire structure must be copied. So, in those cases references are
preferred. If the argument isn't changed by the function, or if the caller
shouldn't change the returned information, the const
keyword
should be used. Consider the following example:
struct Person // some large structure { char name[80], char address[90]; double salary; }; Person person[50]; // database of persons // printperson expects a void printperson (Person const &p) { // reference to a structure // but won't change it cout << "Name: " << p.name << endl << "Address: " << p.address << endl; } // get a person by indexvalue Person const &person(int index) { return person[index]; // a reference is returned, } // not a copy of person[index] int main() { Person boss; printperson (boss); // no pointer is passed, // so variable won't be // altered by the function printperson(person(5)); // references, not copies // are passed here return 0; }
int &func() { static int value; return value; }This allows the following constructions:
func() = 20; func() += func();It is probably superfluous to note that such constructions should normally not be used. Nonetheless, there are situations where it is useful to return a reference. We have actually already seen an example of this phenomenon at our previous discussion of the streams. In a statement like
cout
<<
"Hello"
<< endl;
, the insertion operator returns a reference to
cout
. So, in this statement first the "Hello"
is inserted into
cout
, producing a reference to cout
. Via this reference the endl
is then inserted in the cout
object, again producing a reference to
cout
. This latter reference is not further used.
A number of differences between pointers and references is pointed out in the list below:
int &ref;
ref
refer to?
external
. These
references were initialized elsewhere.
&
is used with a reference, the
expression yields the address of the variable to which the reference
applies. In contrast, ordinary pointers are variables themselves, so the
address of a pointer variable has nothing to do with the address of the
variable pointed to.
struct
s (see
section 2.5.14). Such functions are called
member functions or
methods.
This section discusses how to define such functions.
The code fragment below illustrates a struct
having data fields for a
name and an address. A function print()
is included in the
struct
definition:
struct Person { char name[80], char address[80]; void print(); };The member function
print()
is defined using the structure name
(Person
) and the scope resolution operator (::
):
void Person::print() { cout << "Name: " << name << endl "Address: " << address<< endl; }In the definition of this member function, the function name is preceded by the
struct
name followed by ::
. The code of the function shows how
the fields of the struct
can be addressed without using the type name: in
this example the function print()
prints a variable name
. Since
print()
is a part of the struct
person
, the variable name
implicitly refers to the same type.
This struct
could be used as follows:
Person p; strcpy(p.name, "Karel"); strcpy(p.address, "Rietveldlaan 37"); p.print();The advantage of member functions lies in the fact that the called function can automatically address the data fields of the structure for which it was invoked. As such, in the statement
p.print()
the structure p
is
the `substrate': the variables name
and address
which are used in the
code of print()
refer to the same struct p
.
void, char,
short, int, long, float
and double
. C++ extends these basic types
with several new types: the types
bool
,
wchar_t
,
long long
and
long double
(Cf.
ANSI/ISO draft (1995), par. 27.6.2.4.1 for examples of
these very long types). The type
long long
is merely a double-long
long
datatype. The type
long double
is merely a double-long double
datatype. Apart from these basic types a standard type string
is
available. The datatypes bool
, and wchar_t
are covered in the
following sections, the datatype string
is covered in chapter
4. Note that recent versions of C may also have adopted some of
these newer data types (notably bool
and wchar_t
). Traditionally,
however, C doesn't support them, hence they are mentioned in this section.
Now that these new types are introduced, let's refresh your memory about letters that can be used in literal constants of various types. They are:
E
or e
:
the
exponentiation character in floating point literal values. For example:
1.23E+3
. Here, E
should be pronounced (and interpreted) as: times 10
to the power. Therefore, 1.23E+3
represents the value 1230
.
F
can be used as postfix to a
non-integral numeric constant to indicate a value of type float
, rather
than double
, which is the default. For example: 12.F
(the dot
transforms 12 into a floating point value); 1.23E+3F
(see the previous
example. 1.23E+3
is a double
value, whereas 1.23E+3F
is a
float
value).
L
can be used as prefix to
indicate a character string whose elements are wchar_t
-type
characters. For example: L"hello world"
.
L
can be used as postfix to an
integral value to indicate a value of type long
, rather than int
,
which is the default. Note that there is no letter indicating a short
type. For that a static_cast<short>()
must be used.
U
can be used as postfix to an
integral value to indicate an unsigned
value, rather than an int
.
It may also be combined with the postfix L
to produce an unsigned long
int
value.
void, char, int,
float
and double
. C++ extends these five basic types with several
extra types. In this section the type
bool
is introduced.
The type bool
represents boolean (logical) values, for which the (now
reserved) values
true
and
false
may be used. Apart from these reserved
values, integral values may also be assigned to variables of type bool
,
which are then implicitly converted to true
and false
according to the
following
conversion rules (assume intValue
is an int
-variable, and
boolValue
is a bool
-variable):
// from int to bool: boolValue = intValue ? true : false; // from bool to int: intValue = boolValue ? 1 : 0;Furthermore, when
bool
values are inserted into, e.g., cout
, then
1
is written for true
values, and 0
is written for false
values. Consider the following example:
cout << "A true value: " << true << endl << "A false value: " << false << endl;The
bool
data type is found in other programming languages as
well. Pascal has its type Boolean
, and Java has a boolean
type. Different from these languages, C++'s type bool
acts like a kind
of int
type: it's primarily a documentation-improving type, having just
two values true
and false
. Actually, these values can be interpreted
as enum
values for 1
and 0
. Doing so would neglect the philosophy
behind the bool
data type, but nevertheless: assigning true
to an
int
variable neither produces warnings nor errors.
Using the bool
-type is generally more intuitively clear than using
int
. Consider the following prototypes:
bool exists(char const *fileName); // (1) int exists(char const *fileName); // (2)For the first prototype
(1)
, most people will expect the function to
return true
if the given filename is the name of an existing
file. However, using the second prototype some ambiguity arises: intuitively
the return value 1 is appealing, as it leads to constructions like
if (exists("myfile")) cout << "myfile exists";On the other hand, many functions (like
access()
,
stat()
, etc.)
return 0
to indicate a successful operation, reserving other values to
indicate various types of errors.
As a rule of thumb I suggest the following: if a function should
inform its caller about the success or failure of its task, let the function
return a bool
value. If the function should return success or various
types of errors, let the function return enum values, documenting the
situation when the function returns. Only when the function returns a
meaningful integral value (like the sum of two int
values), let the
function return an int
value.
wchar_t
type is an extension of the char
basic type, to accomodate
wide character values, such as the
Unicode character set.
The
g++
compiler (version 2.95 or beyond) reports
sizeof(wchar_t)
as 4, which easily accomodates all
65,536 different Unicode character values.
Note that a programming language like Java has a data type char
that
is comparable to C++'s wchar_t
type. Java's char
type is
2 bytes wide, though. On the other hand, Java's byte
data
type is comparable to C++'s char
type: one byte. Very convenient....
size_t
type is not really a built-in primitive data type, but a data
type that is promoted by
POSIX as a typename to be used for non-negative
integral values answering questions like `how much' and `how many', in which
case it should be used instead of
unsigned int
. It is not a specific
C++ type, but also available in, e.g., C. Usually it is defined
implictly when a system header file is included. The header file `officially'
defining size_t
in the context of C++ is
cstddef
.
Using size_t
has the advantage of being a conceptual type, rather than
a standard type that is then modified by a modifier. Thus, it improves
the self-documenting value of source code.
Sometimes functions explictly require unsigned int
to be used. E.g., on
amd
-architectures the
X-windows function
XQueryPointer
explicitly
requires a pointer to an unsigned int
variable as one of its arguments. In
this particular situation a pointer to a size_t
variable can't be
used. This situation is exceptional, though.
Other useful bit-represented types also exists. E.g.,
uint32_t
is
guaranteed to hold 32-bits unsigned values. Analogously,
int32_t
holds
32-bits signed values. Corresponding types exist for 8, 16 and 64 bits
values. These types are defined in the header file
stdint.h
.
and const float operator static_cast using and_eq const_cast for or struct virtual asm continue friend or_eq switch void auto default goto private template volatile bitand delete if protected this wchar_t bitor do inline public throw while bool double int register true xor break dynamic_cast long reinterpret_cast try xor_eq case else mutable return typedef catch enum namespace short typeid char explicit new signed typename class extern not sizeof union compl false not_eq static unsignedNote the operator keywords:
and, and_eq, bitand, bitor, compl,
not, not_eq, or, or_eq, xor
and xor_eq
are symbolic alternatives for,
respectively, &&, &=, &, |, ~, !, !=, ||, |=, ^
and ^=
.
C++ has three special keywords which are related to data hiding:
private
,
protected
and
public
. These keywords can be used in the
definition of a struct
. The keyword public
defines all subsequent
fields of a structure as accessible by all code; the keyword private
defines all subsequent fields as only accessible by the code which is part of
the struct
(i.e., only accessible to its member functions). The keyword
protected
is discussed in chapter 13, and is beyond the
scope of the current discussion.
In a struct
all fields are public
, unless explicitly stated otherwise.
Using this knowledge we can expand the struct
Person
:
struct Person { private: char d_name[80]; char d_address[80]; public: void setName(char const *n); void setAddress(char const *a); void print(); char const *name(); char const *address(); };The data fields
d_name
and d_address
are only accessible to the
member functions which are defined in the struct
: these are the functions
setName()
, setAddress()
etc.. This results from the fact
that the fields d_name
and d_address
are preceded by the
keyword private
. As an illustration consider the following code fragment:
Person x; x.setName("Frank"); // OK, setName() is public strcpy(x.d_name, "Knarf"); // error, x.d_name is privateData hiding is implemented as follows: the actual data of a
struct
Person
are mentioned in the structure
definition. The data are accessed by the outside world using special functions,
which are also part of the definition. These member functions control all
traffic between the data fields and other parts of the program and are
therefore also called `interface' functions. The data hiding which is thus
implemented is illustrated in Figure 2.
Also note that the functions setName()
and setAddress()
are
declared as having a char const *
argument. This means that the functions
will not alter the strings which are supplied as their arguments. In the same
vein, the functions name()
and address()
return a char const *
:
the caller may not modify the strings to which the return values point.
Two examples of member functions of the struct
Person
are shown
below:
void Person::setName(char const *n) { strncpy(d_name, n, 79); d_name[79] = 0; } char const *Person::name() { return d_name; }In general, the power of the member functions and of the concept of data hiding lies in the fact that the interface functions can perform special tasks, e.g., checking the validity of the data. In the above example
setName()
copies only up to 79 characters from its argument to the data
member name
, thereby avoiding
array buffer overflow.
Another example of the concept of data hiding is the following. As an
alternative to member functions which keep their data in memory (as do the
above code examples), a runtime library could be developed with interface
functions which store their data on file. The conversion of a program which
stores Person
structures in memory to one that stores the data on disk
would not require any modification of the program using Person
structures. After recompilation and linking the new object module to a new
library, the program will use the new Person
structure.
Though data hiding can be implemented with structs
, more often (almost
always) classes are used instead. A class
refers to the same concept as a
struct, except that a class
uses private access by default, whereas
structs use public access by default. The definition of a class
Person
would therefore look exactly as shown above, except for the fact that instead
of the keyword struct
, class
would be used, and the initial
private:
clause can be omitted. Our typographic suggestion for class
names is to use a capital character as its first character, followed by the
remainder of the name in lower case (e.g., Person
).
struct
, which then require a pointer to the struct
as one of their
arguments. A fragment of an imaginary C header file is given below:
/* definition of a struct PERSON_ */ typedef struct { char name[80]; char address[80]; } PERSON_; /* some functions to manipulate PERSON_ structs */ /* initialize fields with a name and address */ void initialize(PERSON_ *p, char const *nm, char const *adr); /* print information */ void print(PERSON_ const *p); /* etc.. */In C++, the declarations of the involved functions are placed inside the definition of the
struct
or class
. The argument which denotes
which struct
is involved is no longer needed.
class Person { public: void initialize(char const *nm, char const *adr); void print(); // etc.. private: char d_name[80]; char d_address[80]; };The
struct
argument is implicit in C++. A C function call
such as:
PERSON_ x; initialize(&x, "some name", "some address");becomes in C++:
Person x; x.initialize("some name", "some address");
cos(), sin(), tan()
etc. are to be used
accepting arguments in degrees rather than arguments in
radians. Unfortunately, the function name cos()
is already in use, and that
function accepts radians as its arguments, rather than degrees.
Problems like these are usually solved by defining another name, e.g., the
function name cosDegrees()
is defined. C++ offers an alternative
solution: by allowing us to use
namespaces. Namespaces can be considered
as areas or regions in the code in which identifiers are defined which
normally won't conflict with names already defined elsewhere.
Now that the
ANSI/ISO standard has been implemented to a large degree in
recent compilers, the use of namespaces is more strictly enforced than in
previous versions of compilers. This has certain consequences for the setup of
class
header files. At this point in the Annotations this cannot be
discussed in detail, but in section 6.7.1 the construction of
header files using entities from namespaces is discussed.
namespace identifier { // declared or defined entities // (declarative region) }The identifier used in the definition of a namespace is a standard C++ identifier.
Within the
declarative region, introduced in the above code
example, functions, variables, structs, classes and even (nested) namespaces
can be defined or declared. Namespaces cannot be defined within a block. So it
is not possible to define a namespace within, e.g., a function. However, it is
possible to define a namespace using multiple namespace
declarations. Namespaces are called `open'. This means that a namespace
CppAnnotations
could be defined in a file file1.cc
and also in a file
file2.cc
. The entities defined in the CppAnnotations
namespace of
files file1.cc
and file2.cc
are then united in one CppAnnotations
namespace region. For example:
// in file1.cc namespace CppAnnotations { double cos(double argInDegrees) { ... } } // in file2.cc namespace CppAnnotations { double sin(double argInDegrees) { ... } }Both
sin()
and cos()
are now defined in the same
CppAnnotations
namespace.
Namespace entities can be defined outside of their namespaces. This topic is discussed in section 3.7.4.1.
namespace CppAnnotations { double cos(double degrees); double sin(double degrees); }
Entities defined in the anonymous namespace are comparable to C's
static
functions and variables. In C++ the static
keyword can
still be used, but its use is more common in class
definitions (see
chapter 6). In situations where static variables or
functions are necessary, the use of the anonymous namespace is preferred.
The anonymous namespace is a closed namespace: it is not possible to add entities to the same anonymous namespace using different source files.
cos()
defined
in the CppAnnotations
namespace the following code could be used:
// assume the CppAnnotations namespace is declared in the // next header file: #include <CppAnnotations> int main() { cout << "The cosine of 60 degrees is: " << CppAnnotations::cos(60) << endl; }This is a rather cumbersome way to refer to the
cos()
function in the
CppAnnotations
namespace, especially so if the function is frequently
used.
However, in these cases an abbreviated form (just cos()
) can be
used by specifying a
using-declaration. Following
using CppAnnotations::cos; // note: no function prototype, // just the name of the entity // is required.the function
cos()
will refer to the cos()
function in the
CppAnnotations
namespace. This implies that the standard cos()
function, accepting radians, cannot be used automatically anymore. The plain
scope resolution operator can be used to reach the generic cos()
function:
int main() { using CppAnnotations::cos; ... cout << cos(60) // uses CppAnnotations::cos() << ::cos(1.5) // uses the standard cos() function << endl; }Note that a
using
-declaration can be used inside a block. The
using
declaration prevents the definition of entities having the same
name as the one used in the using
declaration: it is not possible to
use a using
declaration for a variable value
in the CppAnnotations
namespace, and to define (or declare) an identically named object in the
block in which the using
declaration was placed:
int main() { using CppAnnotations::value; ... cout << value << endl; // this uses CppAnnotations::value int value; // error: value already defined. }
using
-declaration is the
using-directive:
using namespace CppAnnotations;Following this directive, all entities defined in the
CppAnnotations
namespace are used as if they where declared by using
declarations.
While the using
-directive is a quick way to
import all the names of
the CppAnnotations
namespace (assuming the entities are declared or
defined separately from the directive), it is at the same time a somewhat
dirty way to do so, as it is less clear which entity will be used in a
particular block of code.
If, e.g., cos()
is defined in the CppAnnotations
namespace, the
function CppAnnotations::cos()
will be used when cos()
is called in
the code. However, if cos()
is not defined in the CppAnnotations
namespace, the standard cos()
function will be used. The using
directive does not document as clearly which entity will be used as the
using
declaration does. For this reason, the using
directive is
somewhat deprecated.
`Koenig lookup' refers to the fact that if a function is called without referencing a namespace, then the namespaces of its arguments are used to find the namespace of the function. If the namespace in which the arguments are defined contains such a function, then that function is used. This is called the `Koenig lookup'.
In the following example this is illustrated. The function
FBB::fun(FBB::Value v)
is defined in the FBB
namespace. As shown, it
can be called without the explicit mentioning of a namespace:
#include <iostream> namespace FBB { enum Value // defines FBB::Value { first, second, }; void fun(Value x) { std::cout << "fun called for " << x << std::endl; } } int main() { fun(FBB::first); // Koenig lookup: no namespace // for fun() } /* generated output: fun called for 0 */Note that trying to fool the compiler doesn't work: if in the
namespace FBB
Value
was defined as typedef int Value
then
FBB::Value
would have been recognized as int
, thus causing the Koenig
lookup to fail.
As another example, consider the next program. Here there are two
namespaces involved, each defining their own fun()
function. There is no
ambiguity here, since the argument defines the namespace. So,
FBB::fun()
is called:
#include <iostream> namespace FBB { enum Value // defines FBB::Value { first, second, }; void fun(Value x) { std::cout << "FBB::fun() called for " << x << std::endl; } } namespace ES { void fun(FBB::Value x) { std::cout << "ES::fun() called for " << x << std::endl; } } int main() { fun(FBB::first); // No ambiguity: argument determines // the namespace } /* generated output: FBB::fun() called for 0 */
Finally, an example in which there is an ambiguity: fun()
has two
arguments, one from each individual namespace. Here the ambiguity must be
resolved by the programmer:
#include <iostream> namespace ES { enum Value // defines ES::Value { first, second, }; } namespace FBB { enum Value // defines FBB::Value { first, second, }; void fun(Value x, ES::Value y) { std::cout << "FBB::fun() called\n"; } } namespace ES { void fun(FBB::Value x, Value y) { std::cout << "ES::fun() called\n"; } } int main() { /* fun(FBB::first, ES::first); // ambiguity: must be resolved // by explicitly mentioning // the namespace */ ES::fun(FBB::first, ES::first); } /* generated output: ES::fun() called */
cout, cin, cerr
and
the templates defined in the Standard Template Library, see chapter
17) are now defined in the std
namespace.
Regarding the discussion in the previous section, one should use a using
declaration for these entities. For example, in order to use the cout
stream, the code should start with something like
#include <iostream> using std::cout;Often, however, the identifiers that are defined in the
std
namespace
can all be accepted without much thought. Because of that, one frequently
encounters a using
directive, rather than a using
declaration with the
std
namespace. So, instead of the mentioned using declaration
a
construction like
#include <iostream> using namespace std;is encountered. Whether this should be encouraged is subject of some dispute. Long
using
declarations are of course inconvenient too. So, as a
rule of thumb one might decide to stick to using
declarations, up to
the point where the list becomes impractically long, at which point a
using
directive could be considered.
namespace CppAnnotations { namespace Virtual { void *pointer; } }Now the variable
pointer
is defined in the Virtual
namespace,
nested under the CppAnnotations
namespace. In order to refer to this
variable, the following options are available:
int main() { CppAnnotations::Virtual::pointer = 0; }
using
declaration for CppAnnotations::Virtual
can be
used. Now Virtual
can be used without any prefix, but
pointer
must be used with the Virtual::
prefix:
... using CppAnnotations::Virtual; int main() { Virtual::pointer = 0; }
using
declaration for CppAnnotations::Virtual::pointer
can be used. Now pointer
can be used without any prefix:
... using CppAnnotations::Virtual::pointer; int main() { pointer = 0; }
using
directive or directives can be used:
... using namespace CppAnnotations::Virtual; int main() { pointer = 0; }Alternatively, two separate
using
directives could have been used:
... using namespace CppAnnotations; using namespace Virtual; int main() { pointer = 0; }
using
declarations and using
directives can be used. E.g., a using
directive can be used for
the CppAnnotations
namespace, and a using
declaration can be used for
the Virtual::pointer
variable:
... using namespace CppAnnotations; using Virtual::pointer; int main() { pointer = 0; }
using
directive all entities of that namespace can be used
without any further prefix. If a namespace is nested, then that namespace can
also be used without any further prefix. However, the entities defined in the
nested namespace still need the nested namespace's name. Only by using a
using
declaration or directive the qualified name of the
nested namespace can be omitted.
When fully qualified names are somehow preferred and a long form like
CppAnnotations::Virtual::pointeris at the same time considered too long, a namespace alias can be used:
namespace CV = CppAnnotations::Virtual;
This defines CV
as an alias for the full name. So, to refer to the
pointer
variable, we may now use the construction
CV::pointer = 0;Of course, a namespace alias itself can also be used in a
using
declaration or directive.
A
within the
region of namespace C
, it is possible to define a member of namespace
A::B
within the region of namespace A
.
Note, however, that when a member of a namespace is defined outside of a namespace region, it must still be declared within the region.
Assume the type int INT8[8]
is defined in the
CppAnnotations::Virtual
namespace.
Now suppose we want to define a member function funny
, inside the
namespace CppAnnotations::Virtual
, returning a pointer to
CppAnnotations::Virtual::INT8
. After first defining everything
inside the CppAnnotations::
Virtual
namespace, such a function could be
defined as follows (the examples below use the memory allocation operator
new[]
which will formally be introduced in chapter 7). At this
point it can be assumed to behave somewhat like C's memory allocation
function malloc()
:
namespace CppAnnotations { namespace Virtual { void *pointer; typedef int INT8[8]; INT8 *funny() { INT8 *ip = new INT8[1]; for (int idx = 0; idx < sizeof(INT8) / sizeof(int); ++idx) (*ip)[idx] = (idx + 1) * (idx + 1); return ip; } } }The function
funny()
defines an array of one INT8
vector, and
returns its address after initializing the vector by the squares of the first
eight natural numbers.
Now the function funny()
can be defined outside of the
CppAnnotations::
Virtual
namespace as follows:
namespace CppAnnotations { namespace Virtual { void *pointer; typedef int INT8[8]; INT8 *funny(); } } CppAnnotations::Virtual::INT8 *CppAnnotations::Virtual::funny() { INT8 *ip = new INT8[1]; for (int idx = 0; idx < sizeof(INT8) / sizeof(int); ++idx) (*ip)[idx] = (idx + 1) * (idx + 1); return ip; }In the final code fragment note the following:
funny()
is declared inside of the CppAnnotations::Virtual
namespace.
The definition outside of the namespace region requires us to use
the fully qualified name of the function and of its return type.
Inside the block of the function funny
we are within the
CppAnnotations::
Virtual
namespace, so inside the function fully
qualified names (e.g., for INT8
) are not required any more.
Finally, note that the function could also have been defined in the
CppAnnotations
region. It that case the Virtual
namespace would have
been required when defining funny()
and when specifying its return type,
while the internals of the function would remain the same:
namespace CppAnnotations { namespace Virtual { void *pointer; typedef int INT8[8]; INT8 *funny(); } Virtual::INT8 *Virtual::funny() { INT8 *ip = new INT8[1]; for (int idx = 0; idx < sizeof(INT8) / sizeof(int); ++idx) (*ip)[idx] = (idx + 1) * (idx + 1); return ip; } }