|
HP aC++ Keywords
HP aC++ supports the following list of keywords. Keywords cannot
be abbreviated and must always be entered in lowercase letters.
|
|
- friend
- inline
- mutable
- namespace
- new
- not
- not_eq
- operator
- or
- or_eq
- private
- protected
- public
- reinterpret_cast
|
|
bool Keyword
The keyword bool represents a data type. Variables and
expressions of type bool can have a value of either true or
false. The value of true equals 1.
The value of false equals 0.
Usage:
The ANSI/ISO C++ International Standard states that values of
type bool are either true or false.
There are no signed, unsigned, short, or long bool types or values.
bool values behave as integral types and participate in
integral promotions.
Types bool, char, wchar_t, and the signed and
unsigned integer types are collectively called integral types.
A synonym for integral type is integer type. The representations of
integral types shall define values by use of a pure binary numeration
system.
Example:
void main(){
bool b=true; // Declare a variable of type bool and set it to true.
if (b) // Test value of bool variable.
b=false; // Set it to false.
}
dynamic_cast Keyword
The keyword dynamic_cast is used in expressions to
check the safety of a type cast at runtime. It is the simplest and
most useful form of runtime type identification. You can use it to
cast safely within a class hierarchy based on the runtime type of
objects that are polymorphic types (classes including at least one
virtual function). At runtime, the expression being cast is checked
to verify that it points to an instance of the type being cast to.
Usage:
A dynamic cast is most often used to cast from a base class
pointer to a derived class pointer in order to invoke a function
appearing only in the derived class. Virtual functions are
preferred when their mechanism is sufficient. Usually a
dynamic cast is necessary because the base class is being
specialized, but cannot (or should not) be modified.
Example:
class Base {
virtual void f(); // Make Base a polymorphic type.
// other class details omitted
};
class Derived : public Base {
// class details omitted
};
void Base::f()
{
// define Base function
}
void main()
{
Base *p;
Derived *q;
Base b;
Derived d;
p = &b;
q = dynamic_cast<Derived *> (p); // Yields zero.
p = &d;
q = dynamic_cast<Derived *> (p); // Yields p treated
// as a derived pointer.
}
Static and dynamic casts are used to move within a class hierarchy.
Static casts use only static (compile-time) information to do the
conversions. In this example above, if p is really pointing
to an object of type Derived, either a static or dynamic cast
of p to q yields the same result. This is also true
if p were the null pointer. But, if p is not pointing
to an object of type Derived, a dynamic cast returns zero, and
a static cast returns a stray pointer. Dynamic casts must be done to a
pointer or reference type. For example, if the cast above is written as:
q = dynamic_cast <Derived> (p);
The compile time error message is:
The result type of a dynamic cast must be a pointer or
reference to a complete class; the actual type was Derived.
If you attempt a dynamic cast from a non-polymorphic type, you
will also get a compile-time error. For example:
class Base {
// class details omitted
};
class Derived : public Base {
// class details omitted
};
void main()
{
Base *p;
Derived *q;
Base b;
p = &b;
q = dynamic_cast<Derived *> (p);
}
The above code generates the following compile-time error:
Dynamic down-casts and cross-casts must start from a polymorphic class
(one that contains or inherits a virtual function); but class Base is
not polymorphic.
The syntax of conditions allows declarations in them. For example:
class Base {
virtual void f(); // Make Base a polymorphic type
// other class details omitted
};
class Derived : public Base {
public:
void derivedFunction();
// other class details omitted
};
void Base::f()
{
// Define Base function.
}
void Derived::derivedFunction()
{
}
void main()
{
Base *p = new Derived;
// details omitted
if (Derived *q = dynamic_cast<Derived *> (p))
q->derivedFunction(); // use derived function
}
You can use dynamic casts with references as well. Since a
reference cannot be zero, when the cast fails it raises a
Bad_cast exception.
Before the implementation of the dynamic
cast operator, you could not cast from a virtual base class to
one of its derived classes because there was not enough
information in the object at runtime to do this cast.
After runtime type identification was added, the
information stored in a polymorphic virtual base class is
sufficient to allow a dynamic cast from this base class
to one of its derived classes. For example:
class Base1 {
// Not a polymorphic type.
// additional class details omitted
};
class Base2 {
virtual void f(); // Make Base2 polymorphic.
// additional class details omitted
};
void Base2::f()
{
// Define Base2 function.
}
class Derived : public virtual Base1, public virtual Base2 {
// additional class details omitted
};
void main()
{
Base1 *bp1;
Base2 *bp2;
Derived *dp;
bp1 = new Derived;
bp2 = new Derived;
// dp = (Derived *) bp1; // Problem: compile time error
// Can't cast from virtual base.
// dp = (Derived *) bp2; // Problem: compile time error
// Can't cast from virtual base.
// dp = dynamic_cast<Derived *> bp1; // Problem: compile time error
// Can't cast from
// non-polymorphic type.
dp = dynamic_cast<Derived *> bp2; // OK
}
explicit Keyword
The explicit keyword is used for declaring constructor
functions within class declarations. When these functions
are declared explicit, they cannot be used for implicit
conversions.
Usage:
While constructors taking one argument are often useful
in the design of a class, they can allow inadvertent
conversion in expressions. This can introduce subtle
bugs. The explicit keyword allows a class designer to
prohibit such implicit conversions. It is often used
in the production of class libraries.
Example:
class C {
public:
explicit C(int);
};
C::C(int)
{
// empty definition
}
void main()
{
C c(5); // Legal
c = C(10); // Legal
// c = 15; // Produces a compile time error:
// Message: Cannot assign 'C' with 'int'.
// c + 20; // Produces a compile time error
}
A classic example of this problem is an array class:
class Vector {
public:
Vector(int n); // create a vector of n items
// other class details omitted
};
void main()
{
Vector operator + (Vector, Vector);
Vector v1(10), v2(10); // create two 10 element vectors
// details omitted
v1 = v2 + 5; // Legal - converts int 5 to a 5
// element vector and adds to v2.
// Not something you want to be
// legal
}
With the explicit keyword, the constructor can be
made explicit and the declarations are legal, but the
addition is a compilation error:
class Vector {
public:
explicit Vector(int n); // create a vector of n items
// other class details omitted
};
void main()
{
Vector operator + (Vector, Vector);
Vector v1(10), v2(10); // create two 10 element vectors
// details omitted
// v1 = v2 + 5; // Not legal - generates compile-
// time error
// Message: Illegal types
// associated with operator '+':
// 'Vector' and 'int'.
}
mutable Keyword
The mutable keyword is used in declarations of class members.
It allows certain members of constant objects to be modified in
spite of the constness of the containing object.
Usage:
Often some class members are part of the implementation of the
object, not part of the actual information stored by the object.
Although the information in the object needs to stay unmodified
in a const object, the implementation members may need to change.
These are declared mutable.
An example of this is a use or reference count in an object that
keeps track of the number of pointers referring to it.
Example:
class C {
public:
C();
int i;
mutable int j;
};
C::C() : i(1), j(3)
{
// Define constructor
}
void main()
{
const C c1;
C c2;
// c1.i =0; // Problem: compilation error
// Message: The left side of '=' must be
// a modifiable lvalue.
c1.j = 1; // OK
c2.i = 2; // OK
c2.j = 3; // OK
}
The mutable keyword can only be used on class
data members. It cannot be used for const or static
data members. Notice the difference in the two
pointer declarations below:
class C {
C() { } // define constructor
mutable const int *p; // OK
// mutable pointer to int const
// p in constant C object can be modified
mutable int *const q; // Compile time error
// mutable const pointer to int
// const data member can't be mutable
// Message: 'mutable' may be used only
// in non-static and non-constant data
// member declarations within class
// declarations.
};
namespace and using Keywords
Namespaces were introduced into C++ primarily as a mechanism to
avoid naming conflicts between various libraries. The example
below illustrates how this is achieved.
Every namespace introduces a new scope. By default, names
inside a namespace are hidden from enclosing scopes. Selection of a
particular name can be achieved using the qualified-name syntax.
Namespaces can be nested very much like classes.
#include <stdio.h>
namespace N {
struct Object {
virtual char const* name() const { return "Object from N"; }
};
}
namespace M {
struct Object {
virtual char const* name() const { return "Object from M"; }
};
namespace X { // a nested namespace
struct Object: M::Object { // inherit from a class in the outer space
char const* name() const { return "Object from M::X"; }
};
}
}
int main() {
N::Object o1;
M::Object o2;
M::X::Object o3;
printf("This object is: %s.\n", o1.name());
printf("This object is: %s.\n", o2.name());
printf("This object is: %s.\n", o3.name());
return 0;
}
Connections Across Translation Units
If a type, function, object, etc., is declared inside of
a namespace, then using that entity will require naming
this namespace in some explicit or implicit way; even if the use
happens in another translation unit (or source file).
One unique feature of namespaces is that they can be extended.
The example below shows this as well as the connections between
a namespace extending across different translation units.
The example also illustrates the concept of unnamed namespaces.
These namespaces can only be extended within a translation unit.
Unnamed namespaces in different translation units are unrelated;
hence their names effectively have internal linkage.
The ANSI/ISO C++ International Standard specifies that using static to
indicate internal linkage is deprecated in favor of using namespaces.
#include <stdio.h>
namespace N {
char const* f() { return "f()"; }
}
namespace { // An unnamed namespace
char const* f(double);
} // Names in unnamed namespaces are visible in their surrounding scope.
// They cannot be qualified since the space has no name.
namespace N { // An extension of the first part of namespace N
char const* f(int); // Leave the implementation to another
} // translation unit.
int main() {
printf("Calling: %s.\n", N::f()); // OK, declared and defined above
printf("Calling: %s.\n", N::f(7)); // OK, declared above (defined elsewhere)
printf("Calling: %s.\n", f(3.0)); // OK, declared above (defined below)
return 0;
}
namespace { // An extension of the unnamed namespace in this translation unit
char const* f(double) { return "f(double) in main() translation unit"; }
}
Auxiliary Translation Unit
Following is an auxiliary translation unit that illustrates
how namespaces interact across translation units.
namespace { // An unnamed namespace unrelated to the
// one in the other translation units.
char const* f(double) { return "f(double) in auxiliary translation unit"; }
}
namespace N { // This namespace is the same as the one in the main()
// translation unit. We implement f(int) here.
char const* f(int) { return "f(int) defined in auxiliary translation unit"; }
}
using-declarations and using-directives
The C++ provides two alternatives to explicitly qualifying names in
namespaces. These are the using-declaration and the using-directive.
using-declaration
A using-declaration introduces a declaration in the current scope as follows:
using N::x; // Where N is a namespace, x is a name in N
After this declaration, all uses of x in this scope are taken to
defer to N::x. (The N:: prefix is no longer required.)
If another declaration of x were introduced in the same scope, for example:
int x;
then a compiler error would occur.
using-directive
The using-directive directs the lookup for names not
declared in current scope, for example:
using namespace N; // If not found, lookup names in namespace N
If x is a name in namespace N, but another declaration
of x is present in the current scope, for example:
int x;
a compiler error is not necessarily emitted. Only if that name is used will an ambiguity occur.
Note: using-directives are transitive. If you specify a
using-directive to one namespace which itself specifies a
directive to another namespace, then names used in your scope
will also be looked up in that other namespace.
Using namespace directives can be a powerful means to migrate code to
libraries that use namespaces. Occasionally, however, they may
silently make unwanted names visible. It is therefore often
suggested not to use using-directives unless the alternatives
are very inconvenient.
#include <stdio.h>
namespace N {
char const* f() { return "N::f()"; }
char const* f(double) { return "N::f(double)"; }
char const* g() { return "N::g()"; }
}
char const* g(double) {
using N::f; // Declare all f's in namespace N
return f(2.0);
}
namespace M { // Illustrate how using-directives
using namespace N; // are transitive
}
int main() {
using namespace N;
printf("Calling: %s.\n", f()); // calls N::f()
printf("Calling: %s.\n", g(1.0)); // calls ::g(double)
// which calls
// N::f(double)
printf("Calling: %s.\n", N::g()); // calls N::g()
printf("Calling: %s.\n", M::f()); // calls N::f()
return 0;
}
typeid Keyword
The typeid keyword is an operator, called the type identification
operator, used to access type information at runtime. The
operator takes either a type name or an expression and
returns a reference to an instance of type_info, a
standard library class.
Usage:
You can use runtime type identification when you need to know the
exact type of an object. This might, for example, be necessary to
find the name of the object class for diagnostic output. It can
be used to perform some standard service on an object such
as via a database or I/O system.
Example:
type_info Class
typeid Example Code with Discussion
# include <iostream.h>
# include <typeinfo>
class Base {
virtual void f(); // Must have a virtual function to
// be a polymorphic type.
// additional class details omitted
};
class Derived : public Base {
// class details omitted
};
void Base::f()
{
// Define function from Base.
}
void main ()
{
Base *p;
// Code which does either
// p = new Base; or
// p = new Derived;
// Note that this is NOT a good design for this functionality.
// Virtual functions would be better.
if (typeid(*p) == typeid(Base))
cout << "Base Object\n";
else if (typeid(*p) == typeid(Derived))
cout << "Derived Object\n";
else
cout << "Another Kind of Object\n";
}
If a typeid operation is performed on an expression
that is not a polymorphic type (a class which declares or
inherits a virtual function), the operation returns the
static (compile-time) type of the expression.
In the example above, if class Base did not include the virtual
function f(), typeid(p) would always yield the type Base.
The style of programming used in the above example might be called a
typeid switch statement. It is not generally a reasonable
design. One alternative is to use a virtual function in a base class
specialized in each of its derived classes. In some cases, this
may not be possible, for example, when the base class is provided
by a library for which source code is not available.
In other cases it may not be desirable, for example, some base class
interfaces might be too big if all derived class functionality is included.
You can rewrite the above example, using virtual functions, as:
class Base {
virtual void outputType() { cout << "Base Object\n"; }
// additional class details omitted
};
class Derived : public Base {
virtual void outputType() { cout << "Derived Object\n"; }
// additional class details omitted
};
void main ()
{
Base *p;
// code which does either
// p = new Base; or
// p = new Derived;
p->outputType();
}
A second alternative is to use a dynamic cast. In many cases,
this alternative is less desirable than using virtual functions,
but it is better than a typeid switch statement in nearly every case.
There is a subtle difference between this alternative and
the typeid switch statement above. The typeid operation
allows access to the exact type of an object; a dynamic cast returns
a non-zero result for the target type or a type publicly derived from it.
<>
You can rewrite the above example as follows using dynamic casts:
class Base {
virtual void f(); // Must have a virtual function to
// be a polymorphic type.
// additional class details omitted
};
class Derived : public Base {
// class details omitted
};
void Base::f()
{
// Define function from Base.
}
void main ()
{
Base *p;
// code which does either
// p = new Base; or
// p = new Derived;
if (dynamic_cast <Derived *> (p))
cout << "Derived (or class derived from Derived) Object\n";
else
cout << "Base Object\n";
}
typename Keyword
Use the typename keyword in template declarations to specify
that a qualified name is a type, not a class member.
Usage:
This construct is used to access a nested class in the template
parameter class as a type in a declaration within the template.
Example:
template<class T>
class C1 {
// class details omitted
// T::C2 *p; // Problem: flagged as compile-time
// error. T is a type, but T::C2 is not.
// Message: 'C2' is used as a type, but
// has not been defined as a type.
typename T::C2 *p; // Solution: the keyword typename flags
// the qualified name T::C2 as a type.
};
class C {
// details omitted
class C2 {
//details omitted
};
};
void main ()
{
C1<C> c;
}
In a template, a name is not taken to be a type unless it is
explicitly declared as one. Ways to declare a name as a type include:
- Use it as the argument to the template (T below):
template<class T>
class C {
// Additional details omitted
};
- Use it as the name of the template (C below):
template<class T>
class C {
// Additional details omitted
};
-
Declare a class as a member of the class template (C2 below):
template<class T>
class C1 {
class C2;
// Additional details omitted
};
-
Declare a class in the context the template is declared within (C1 below):
class C1;
template<class T>
class C2 {
// details omitted
};
volatile Keyword
The keyword volatile is used in declarations. It tells the
compiler not to do aggressive optimization because a value might be
changed in ways the compiler cannot detect.
This keyword is part of the ANSI C standard with the same syntax and
semantics.
Usage:
Objects that are hardware addresses or those used by concurrently
executing pieces of code are frequently declared volatile.
For example, an address used for the current clock time, objects
used by a signal handler and objects used for memory mapped I/O.
Note: You can declare an identifier to be both const
and volatile. This declares a value that the program cannot
change but can be changed by some means external to the program
(such as by a piece of hardware like a clock).
Example:
class C {
public: // public to make example simpler
volatile int i;
// other class details omitted
};
C someData[10];
void main ()
{
int j = someData[5].i;
j = someData[5].i; // Without the volatile specifier, the
// compiler could optimize these two
// statements into one. With it, it must
// execute both in case the i field of
// someData[5] has changed by some
// other means.
}
wchar_t Keyword
Wide (or multibyte) characters can be declared with the data
type wchar_t. It is an integral type that can represent all
the codes of the largest character set among the supported locales
defined in the localization library. This keyword is a typedef
of the ANSI C standard.
Usage:
This type was added to maintain ANSI C compatibility and to accomodate
foreign (principally Oriental) character sets.
Example:
In the following example, literals of type wchar_t consist
of the character L followed by a character constant
in single quotes.
void main()
{
wchar_t ch = L'a';
}
wchar_t must be implemented the same as another integral
type. In other words, it must have the same size, signedness and
alignment requirements. It promotes to the smallest integral type
when used in an expression and cannot have a signed or unsigned
modifier.
The standard library includes a string of wide characters known as
wstring. The IOStream library supports I/O of wide characters.
In ANSI C, wchar_t is a synonym for another type, declared
using a typedef in a standard header file.
Overloading new[ ] and delete[ ] for Arrays
HP aC++ defines new and delete operators for arrays that are
different from those used for single objects. These operators,
operator new[ ] ( ) and operator delete[ ] ( ), can be overloaded
both globally and in a class. If you use operator new( ) to
allocate memory for a single object, you should use
operator delete( ) to deallocate this memory. If you use
operator new[ ] ( ) to allocate an array, you should use
operator delete[ ] ( ) to deallocate it.
Usually, the allocation and deallocation of operators is overloaded
for a particular class, not globally. This overloading allows you
to put all instances of a particular class on a class-specific heap.
You can then take control of allocation either for efficiency or
to accomplish other storage management functions, for example garbage
collection.
If allocation and deallocation of single objects is overloaded, you
may or may not want to overload the operators for arrays. If the
overloading was done for efficiency, it may be that for arrays
the default operator is the most efficient.
Example:
# include <iostream.h>
class C {
public:
void* operator new[ ] (size_t); // new for arrays
void operator delete[ ] (void*); // delete for arrays
// additional class details omitted
};
void* C::operator new[ ] (size_t allocSize)
{
cout << "Use operator new[ ] from class C\n";
// here, real usage would include allocation
return ::operator new[ ] (allocSize); // global operator
} // for this simple example
void C::operator delete[ ] (void *p)
{
cout << "Use operator delete[ ] from class C\n";
// here, real usage would include deallocation
::operator delete[ ] (p); // global operator
} // for this simple example
void main()
{
C *p;
p = new C[10];
delete[ ] p;
}
Notice that the new operator takes a class with an array specifier
as an argument. The compiler uses the class and array dimension
to provide the size_t argument. In the example above, the
argument provided is ten times the size of a class C object.
Also, the operator must return a void* which the compiler
converts to the class type. The void constructor for the class
(if one exists) is invoked to initialize the elements in the array.
Multidimensional arrays can be allocated and deallocated with these
operators. The operator is used with several array dimensions, and
the compiler provides the size_t argument which is the space
required for the entire array.
Example:
// call C::operator new[ ] ( ) with
// an argument of 10 * 20 * sizeof(C)
p = new C [10] [20];
Additional arguments can be provided to this operator new just as
for the operator for single objects. In this way, the operator can be overloaded
in a class. The additional arguments can be used by the storage allocation
scheme for additional storage management.
The global new and delete for both arrays and single
objects are provided in the Standard C++ Library. This library also
provides a version of new for arrays and single objects that takes
a second void* argument and constructs the object at that address.
Standards Related Options
Options related to the ANSI/ISO 9899-1990 Standard for
the C Programming Language and ANSI/ISO International Standard
and ISO/IEC 14882 Standard for the C++ programming Language are
accepted by the compiler.
Refer to Standards Related Options for more information.
Standard Exception Classes
Classes are provided in the Standard C++ Library to report program
errors. These classes are declared in the <stdexcept> header. All of
these classes inherit from a common base class named exception. The
two classes logic_error and runtime_error inherit from
exception and serve as base classes for more specific errors.
These classes provide a common framework for the way errors are handled
in a C++ program. System-specific error handling can be provided by
creating classes that inherit from these standard exception classes.
Example:
# include <stdexcept>
# include <iostream>
# include <string>
void f()
{
// details omitted
throw range_error(string("some info"));
}
void main()
{
try {
f();
}
catch (runtime_error& r) {
// handle any kind of runtime error including range_error
cout << r.what() << '\n';
}
}
The class logic_error defines objects thrown as exceptions
to report errors due to the internal logic of the program. The errors
are presumably preventable and detectable before execution.
Examples are violations of logical preconditions or class invariants.
The subclasses of logic_error are:
- domain_error (The operation requested is inconsistent with the state of the object it is applied to.)
- invalid_argument
- length_error (An attempt to create an object whose size equals or exceeds allowed size.)
- out_of_range (An argument value not in the expected range.)
Runtime errors are due to events out of the scope of the program.
They cannot be predicted before they happen. The subclasses of
runtime_error are:
- range_error
- overflow_error (arithmetic overflow)
The exception class includes a void constructor, a copy constructor,
an assignment operator, a virtual destructor, and a function what()
that returns an implementation-defined character string. None of these
functions throw any exceptions.
Each of the subclasses includes a constructor taking an instance of
the Standard C++ Library string class as an argument. They
initialize an instance such that the function what(), when
applied to the instance, returns a value equal to the argument to
the constructor.
Standard Exceptions
The following exceptions are thrown by the Standard C++ Library:
- operator new () and operator new [ ] throw a bad_alloc exception when they cannot obtain a block of storage.
- A dynamic_cast expression throws a bad_cast exception when a cast to a reference type fails.
- Operator typeid throws a bad_type exception when a pointer to a typeid expression is zero.
- A bad_exception exception can be thrown when the unexpected handler function is invoked by unexpected().
Note: If no catch clauses are available to catch these exceptions,
the default action is program termination with a call to abort().
(Using the +noeh option does not disable the exceptions
thrown by these library functions.)
type_info Class
type_info is a class in the standard header file <typeinfo>.
A reference to an instance of this class is returned by the typeid
operation. Implementations may differ in the exact details of this class,
but in all cases it is a polymorphic type (has virtual functions)
that allows comparisons and a way to access the name of the type.
This class is useful for diagnostic information and for implementing
services on objects where it is necessary to know the exact
type of the object.
Example:
# include <iostream.h>
# include <typeinfo>
class Base {
virtual void f(); // Must have a virtual function
// to be a polymorphic type
// additional class details omitted
};
class Derived : public Base {
// class details omitted
};
void Base::f()
{
// Define function from Base.
}
void main ()
{
Base *p;
// code which does either
// p = new Base; or
// p = new Derived;
if (typeid(*p) == typeid(Base)) // Standard requires
// comparison as part of
// this class.
cout << "Base Object\n";
cout << typeid(*p).name() << '\n'; // Standard requires access to
// the name of the type.
}
The standard requires the class type_info to be polymorphic.
You cannot assign or copy instances of the class (the copy constructor
and assignment operators are private). The interface must include:
int operator == (const type_info&) const
int operator !=( const type_info&) const
const char * name() const
int before (const type_info&) const
The operators allow comparison of object types.
The name() function allows access to the character
string representing the name of the object. The before function
allows types to be sorted. This allows them to be accessed
through hash tables. The before function is not a lexical ordering;
it might not yield the same results. The name() now returns
the mangled name of a type per the C++ ABI.
This string cannot be demangled with __cxa_demangle.
|