Derived Types

Derived types are new types that can be used in a program. They can conceptually be divided into types that are directly derived and types that are composed of other types. Both types are discussed in this section.

Directly Derived Types

New types derived directly from existing types are types that point to, refer to, or (in the case of functions) transform type data to return a new type. These types are discussed in the sections that follow.

Arrays of Variables or Objects

Arrays of variables or objects can contain a specified number of a particular type. For example, an array derived from integers is an array of type int. The following code sample declares and defines an array of 10 int variables and an array of 5 objects of class SampleClass:

int ArrayOfInt[10];

SampleClass aSampleClass[5];

Functions

Functions take zero or more arguments of given types and return objects of a specified type (or return nothing, if the function has a void return type).

Pointers of a Given Type

Pointers to variables or objects select an object in memory. The object can be global, local (or stack-frame), or dynamically allocated. Pointers to functions of a given type allow a program to defer selection of the function used on a particular object or objects until run time. The following example shows declaration and definition of a pointer to a variable of type char:

char *szPathStr = new char[_MAX_PATH];

References to Objects

References to objects provide a convenient way to access objects by reference but use the same syntax required to access objects by value. The following example demonstrates how to use references as arguments to functions, and as return types of functions:

BigClassType &func( BigClassType &objname )

{

objname.DoSomething(); // Note that member-of operator(.)

// is used.

objname.SomeData = 7; // Data passed by non-const

// reference is modifiable.

return objname;

}

The important points about passing objects to a function by reference are:

Public member data can be read or modified. See Chapter 10, “Member-Access Control,” for information about access specifiers such as public.

The syntax for accessing members of class, struct, and union objects is the same as if they were passed by value: the member-of operator (.).

The objects are not copied prior to the function call; their addresses are passed. This can reduce the overhead of the function call.

Additionally, functions that return a reference need only return the address of the object to which they refer, instead of a copy of the whole object.

Although the preceding example describes references only in the context of communication with functions, references are not constrained to this use. Consider, for example, a case where a function needs to be an l-value—a common requirement for overloaded operators:

class Vector

{

public:

Point &operator[]( int nSubscript ); // Function returns a

// reference type

...

};

The preceding declaration specifies a user-defined subscript operator for class Vector. In an assignment statement, two possible conditions occur:

Vector v1;

int i;

Point p;

v1[7] = p; // Vector used as an l-value

p = v1[7]; // Vector used as an r-value

The latter case, where v1[7] is used as an r-value, can be implemented without use of references. However, the former case, where v1[7] is used as an l-value, cannot be implemented easily without functions that are of reference type. Conceptually, the last two statements in the preceding example translate to the following code:

v1.operator[]( 7 ) = 3; // Vector used as an l-value

i = v1.operator[]( 7 ); // Vector used as an r-value

When viewed in this way, it is easier to see that the first statement must be an l-value to be semantically correct on the left side of the assignment statement.

For more information about overloading, and about overloaded operators in particular, see “Overloaded Operators” in Chapter 12, on topic .

Another use for references is in declaring a const reference to a variable or object. A reference declared as const retains the efficiency of passing an argument by reference, while preventing the called function from modifying the original object. Consider the following code:

// IntValue is a const reference.

void PrintInt( const int &IntValue )

{

printf( "%d\n", IntValue );

}

Reference initialization is different from assignment to a variable of reference type. Consider the following code:

int i = 7;

int j = 5;

// Reference initialization

int &ri = i; // Initialize ri to refer to i.

int &rj = j; // Initialize rj to refer to j.

// Assignment

ri = 3; // i now equal to 3.

rj = 12; // j now equal to 12.

ri = rj; // i now equals j (12).

Constants

See “Literals” in Chapter 1, on topic for more information about the various kinds of constants allowed in C++.

Pointers to Class Members

These pointers define a type that points to a class member of a particular type. Such a pointer can be used by any object of the class type or any object of a type derived from the class type.

Use of pointers to class members enhances the type safety of the C++ language. Several new operators and constructs are used with pointers to members, as shown in Table 2.5.

Table 2.5 Operators and Constructs Used with Pointers to Members

Operator or
Construct

Syntax

Use

::* type::*ptr-name Declaration of pointer to member. The type specifies the class name, and ptr-name specifies the name of the pointer to member. Pointers to members may be initialized. For example:
    MyType::*pMyType = &MyType::i;
.* obj-name.*ptr-name Dereference a pointer to a member using an object. For example:
    int j = Object.*pMyType;
–>* obj-ptr–>*ptr-name Dereference a pointer to a member using a pointer to an object. For example:
    int j = pObject->*pMyType;

Consider this example that defines a class AType and the derived type pDAT, which points to the member I1:

#include <iostream.h>

// Define class AType.

class AType

{

public:

int I1;

Show() { cout << I1 << "\n"; }

};

// Define a derived type pDAT that points to I1 members of

// objects of type AType.

int AType::*pDAT = &AType::I1;

int main()

{

AType aType; // Define an object of type AType.

AType *paType = &aType; // Define a pointer to that object.

int i;

aType.*pDAT = 7777; // Assign to aType::I1 using .* operator.

aType.Show();

i = paType->*pDAT; // Dereference a pointer using .-> operator.

cout << i << "\n";

return 0;

}

The pointer to member pDAT is a new type derived from class AType. It is more strongly typed than a “plain” pointer because it points only to int members of class AType (in this case, I1). Pointers to static members are plain pointers rather than pointers to class members. Consider the following example:

class HasStaticMember

{

public:

static int SMember;

};

int HasStaticMember::SMember = 0;

int *pSMember = &HasStaticMember::SMember;

Note that the type of the pointer is “pointer to int,” and not “pointer to HasStaticMember::int.”

Pointers to members can refer to member functions as well as member data. Consider the following code:

#include <stdio.h>

// Declare a base class, A, with a virtual function, Identify.

// (Note that in this context, struct is the same as class.)

struct A

{

virtual void Identify() = 0; // No definition for class A.

};

// Declare a pointer to the Identify member function.

void (A::*pIdentify)() = &A::Identify;

// Declare class B derived from class A.

struct B : public A

{

void Identify();

};

// Declare class C derived from class A.

struct C : public A

{

void Identify();

};

// Define Identify functions for classes B and C.

void B::Identify()

{

printf( "Identification is B::Identify\n" );

}

void C::Identify()

{

printf( "Identification is C::Identify\n" );

}

int main()

{

B BObject; // Declare objects of type B

C CObject; // and type C.

A *pA; // Declare pointer to type A.

pA = &BObject; // Make pA point to an object of type B.

(pA->*pIdentify)(); // Call Identify function through pointer

// to member pIdentify.

pA = &CObject; // Make pA point to an object of type C.

(pA->*pIdentify)(); // Call Identify function through pointer

// to member pIdentify.

return 0;

}

The output from this program is:

Identification is B::Identify

Identification is C::Identify

The function is called through a pointer to type A. However, because the function is a virtual function, the correct function for the object to which pA refers is called.

Composed Derivative Types

The following sections discuss composed derivative types. Information about aggregate types and initialization of aggregate types can be found in “Initializing Aggregates” in Chapter 7, on topic .

Classes

Classes are a composite group of member objects, functions to manipulate these members, and (optionally) access-control specifications to member objects and functions.

By grouping composite groups of objects and functions in classes, C++ allows programmers to create derivative types that define not only data but also the behavior of objects.

Class members default to private access and private inheritance. Classes are covered in Chapter 8, “Classes”; access control is covered in Chapter 10, “Member-Access Control.”

Structures

C++ structures are the same as classes, except that all member data and functions default to public access, and inheritance defaults to public inheritance.

For more information about access control, see Chapter 10, “Member-Access Control.”

Unions

Unions allow programmers to define types capable of containing different kinds of variables in the same memory space. The following code shows how you can use a union to store several different types of variables:

// Declare a union that can hold data of types char, int, long,

// float, double, or char *.

union ToPrint

{

char chVar;

int iVar;

long lVar;

float fVar;

double dVar;

char *szVar;

};

// Declare an enumerated type that describes what type to print.

enum PrintType { CHAR_T, INT_T, LONG_T,

FLOAT_T, DOUBLE_T, STRING_T };

void Print( ToPrint Var, PrintType Type )

{

switch( Type )

{

case CHAR_T:

printf( "%c", Var.chVar );

break;

case INT_T:

printf( "%d", Var.iVar );

break;

case LONG_T:

printf( "%ld", Var.lVar );

break;

case FLOAT_T:

printf( "%d", Var.fVar );

break;

case DOUBLE_T:

printf( "%f", Var.dVar );

break;

case STRING_T:

printf( "%s", Var.szVar );

break;

}

}