Standardizing Your Code

HP aC++ A.03.30 largely conforms to the ISO/IEC 14882 Standard for the C++ Programming Language (the international standard for C++). Choose from the following for more information:

Migration


HP aC++ Keywords

  • or
  • or_eq
  • private
  • protected
  • public
  • reinterpret_cast
  • static_cast
  • template
  • this
  • throw
  • true
  • try
  • typeid
  • typename
  • using
  • virtual
  • volatile (also an ANSI C keyword)
  • wchar_t
  • xor
  • xor_eq

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 can't (or shouldn't) be modified.

Example Code with Discussion

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 the 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 generates a compile-time error stating that: 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 can't 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. Once runtime type identification was added, however, 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 Code with Discussion

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 Code with Discussion

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

Basic Concepts

Namespaces were introduced into C++ primarily as a mechanism to avoid naming conflicts between various libraries. The example below illustrates how this is achieved.

As can be seen, 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 somewhat 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 so-called 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. In fact, 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"; } }

An 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-derective
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.

CAUTION: 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 also might be used to perform some standard service on an object such as via a database or I/O system.

For More Information

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 could 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 could 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"; }

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 couldn't 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. Examples are an address used for the current clock time, objects used by a signal handler, or 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 which can be changed by some means external to the program (such as by a piece of hardware like a clock).

Example Code with Discussion

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 multi-byte) 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 was part of the ANSI C standard.

Usage

This type was added to maintain ANSI C compatibility and to accomodate foreign (principally Oriental) character sets.

Example Code with Discussion

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.

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 Code

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; }

Discussion

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:


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.

Usage

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.

For More Information

Example Code with Discussion

# 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. For 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.

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.

Usage

These classes provide a common framework for the way errors are handled in a C++ program. Systen-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'; } }

Discussion

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:

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:

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.

Exceptions Thrown by the Standard C++ Library

The following exceptions are thrown by the Standard C++ Library. CAUTION: If no catch clauses are available to catch these exceptions, the default action is program termination with a call to abort(). (Note that using the +noeh option does not disable the exceptions thrown by these library functions.)

Usage

You need to write try/catch clauses to handle the standard exceptions. For an example, refer to Memory Allocation Failure and operator new.

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.

Usage

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 can't 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

Unsupported Functionality

Functionality defined in the ANSI/ISO C++ International Standard and not supported in this release of HP aC++ is listed below. Library functionality is listed separately.

NOTE: These are not all inclusive lists.

Functionality Rogue Wave Standard C++ Library 2.2.1 Rogue Wave Standard C++ Library 1.2.1 libc.sl and libc.a (HP-UX System Libraries)
<allocator> Yes. Provided as a class rather than a template. Not applicable.
<cstring> Yes. The following C++ overloaded functions are not provided, instead, ANSI C signatures are implemented.
  • memchr
  • strchr
  • strpbrk
  • strrchr
  • strstr
Not applicable.
<cwchar> Yes. The following C++ overloaded functions are not provided, instead, ANSI C signatures are implemented.
  • wcschr
  • wcspbrk
  • wcsrchr
Missing functions:
  • wcsstr
  • wmemchr
For missing functions, see wide character support in this table.
<cwctype> Partial support. Partial support. See wide character support in this table.
<functional> Yes. The following types are not provided:
  • mem_fun_t
  • mem_fun1_t
  • mem_fun1_ref_t
  • mem_fun_ref_t
Not applicable.
<iostream> Yes. Not templatized and the following headers are not provided:
  • <fstream>
  • <iostream>
  • <istream>
  • <ostream>
  • <streambuf>
  • <sstream>
  • <iomanip>
  • <ios>
  • <iosfwd>
Not applicable.
<iterator> Yes. iterator template is not provided. Not applicable.
<locale > Yes. Not provided. Not applicable.
printf(3) formats Not applicable. Not applicable. %ls and %lc are not provided
<utility> Yes. rel_ops namespace is not supported. Not applicable.
<valarray> Yes. Not provided. Not applicable.
wide character support Not applicable. Not applicable. The following functions are not provided:
  • btowc
  • fwide
  • fwprintf
  • fwscanf
  • mbrlen
  • mbrtowc
  • mbsinit
  • mbsrtowcs
  • swprintf
  • swscanf
  • towctrans
  • vfwprintf
  • vswprintf
  • vwprintf
  • wcrtomb
  • wcsrtombs
  • wcsstr
  • wctob
  • wctrans
  • wmemchr
  • wmemcmp
  • wmemcpy
  • wmemmove
  • wmemset
  • wprintf
  • wscanf