Home > Articles > Programming > C/C++

📄 Contents

  1. Programming in C++Builder
  2. Better Programming Practices in C++Builder
  3. Further Reading
  4. Summary

Better Programming Practices in C++Builder

This section looks at some ways to improve how you write C++ code in C++Builder. Entire books are devoted to better C++ programming, and you are encouraged to read such texts to deepen your understanding of C++. A list of suggested reading is given at the end of this chapter. The topics discussed here are those that have particular relevance to C++Builder and those that are often misunderstood or misused by those new to C++Builder.

Use a String Class Instead of char*

Say goodbye to char* for string manipulation. Use either the string class provided by the C++ Standard Library or the VCL's native string class AnsiString (which has been conveniently typedefed to String). You also could use both. You can access the C++ Standard Library's string class by including the statement

#include <string>

at the top of your code. If portability is a goal, this is the string class to use. Otherwise, use AnsiString, which has the advantage that it is the string representation used throughout the VCL. This allows your code to work seamlessly with the VCL. You should endeavor to become familiar with the methods that AnsiString offers. Because strings are required so often, this will pay itself back in terms of improved use and efficiency.

For circumstances in which an old-style char* string is required, such as to pass a parameter to a Win32 API call, both string classes offer the c_str() member function, which returns such a string. In addition, the AnsiString class also offers the popular old-style sprintf() and printf()functions (for concatenating strings) as member functions. It offers two varieties of each: a standard version and a cat_ version. The versions differ in that the cat_ version adds the concatenated string to the existing AnsiString, and the standard version replaces any existing contents of the AnsiString. The difference between the sprintf() and printf() member functions is that sprintf() returns a reference to the AnsiString, and printf() returns the length of the final formatted string (or the length of the appended string, in the case of cat_printf). The function declarations are

int __cdecl printf(const char* format, ...);
int __cdecl cat_printf(const char* format, ...);
AnsiString& __cdecl sprintf(const char* format, ...);
AnsiString& __cdecl cat_sprintf(const char* format, ...);

These member functions ultimately call vprintf() and cat_vprintf() in their implementation. These member functions take a va_list as their second parameter, as opposed to a variable argument list. This would require the addition of the #include <stdarg.h> statement in your code. The function declarations are

int __cdecl vprintf(const char* format, va_list paramList);
int __cdecl cat_vprintf(const char* format, va_list paramList);

The respective printf() and sprintf() functions perform the same task, differing only in their return types. As a result, this is the only criterion that is required when deciding which of the two to use.


Caution - Note that the printf() and sprintf() AnsiString member functions in C++Builder version 4 are the same as the cat_printf() and cat_sprintf() functions in version 5, not the printf() and sprintf() AnsiString member functions. Care should be taken when converting code between the two versions.


Understand References and Use Them Where Appropriate

References are often misunderstood and therefore are not used as often as they should be. Often it is possible to replace pointers with references, making the code more intuitive and easier to maintain. This section looks at some of the features of references and when they are most appropriately used. The reason for the abundance of pointer parameters in the VCL in C++Builder is also discussed.

A reference always refers to only one object, its referent, and it cannot be re-bound to refer to a different object ("object" in this context includes all types). A reference must be initialized on creation, and a reference cannot refer to nothing (NULL). Pointers, on the other hand, can point to nothing (NULL), can be re-bound, and do not require initialization on creation. A reference should be considered an alternative name for an object, whereas a pointer should be considered an object in itself. Anything that is done to a reference is also done to its referent and vice versa. This is because a reference is just an alternative name for the referent; they are the same thing. We can see therefore that references, unlike pointers, are implicitly dereferenced.

The following code shows how a reference can be declared:

int X = 12; // Declare and initialize int X to 12

int& Y = X; // Declare a reference to an int, i.e. Y, and
      // initialize it to refer to X

If we change the value of Y or X, we also change the value of X or Y, respectively, because X and Y are two names for the same thing. Another example of declaring a reference to a dynamically allocated variable is

TBook* Book1 = new TBook(); // Declare and create a TBook object

TBook& Book2 = *Book1;   // Declare a TBook reference,
              // i.e. Book2, and initialize it
              // to refer to the object pointed
              // by Book1
The object pointed to by Book1 is the referent of the reference Book2.

One of the most important uses for references is the passing of user-defined types as parameters to functions. A parameter to a function can be passed by reference by making the parameter a reference and calling the function as if it were passed by value. For example, the following function is the typical swap function for two ints:

void swap(int& X, int& Y)
{
  int temp;
  temp = X;
  X = Y;
  Y = temp;
}

This function would be called as follows:

int Number1 = 12;
int Number2 = 68;

Swap(Number1, Number2);

// Number1 == 68 and Number2 == 12

Number1 and Number2 are passed by reference to swap, and therefore X and Y become alternative names for Number1 and Number2, respectively, within the function. What happens to X also happens to Number1 and what happens to Y also happens to Number2. A predefined type such as an int should be passed by reference only when the purpose is to change its value; otherwise, it is generally more efficient to pass by value. The same cannot be said for user-defined types (classes, structs, and so on). Rather than pass such types to functions by value, it is more efficient to pass such types by const reference or, if the type is to be changed, then by non-const reference or pointer. For example:

void DisplayMessage(const AnsiString& message)
{
  //Display message. 
  // message is an alias for the AnsiString argument passed
  // to the function. No copy is made and the const qualifier
  // states that the function will not (cannot) modify message 
}

is better than:

void DisplayMessage(AnsiString message)
{
  //Display message.
  // message is a copy of the AnsiString argument passed
}

The first function is better for two reasons. First, the AnsiString parameter is passed by reference. This means that when the function is called, the AnsiString used as the calling argument is used, because only a reference is used by the function. The copy constructor of AnsiString does not need to be invoked (as it would be on entering the second function), and neither does the destructor, as it would be at the end of the second function when message goes out of scope. Second, the const keyword is used in the first function to signify that the function will not modify message through message. Both functions are called in the same way:

AnsiString Message = "Hello!";

DisplayMessage(Message);

However, the first is safer and faster. Note that the calling code need not be directly affected.

Functions can also return references, which has the side effect of the function becoming an lvalue (a value that can appear on the left side of an expression) for the referent. This also allows operators to be written that appear on the left side of an expression, such as the subscript operator. For example, given the Book class, an ArrayOfBooks class can be defined as follows:

class Book
{ 
  public:
     Book();
     int NumberOfPages;
};

class ArrayOfBooks
{
 private:
     static const unsigned NumberOfBooks = 100;
  public:
     Book& operator[ ] (unsigned i);
};

In this case, an instance of ArrayOfBooks can be used just like a normal array. Elements accessed using the subscript operator can be assigned to and read from, such as in the following:

ArrayOfBooks ShelfOfBooks;
unsigned PageCount = 0;

ShelfOfBooks[0].NumberOfPages = 45;     // A short book!
PageCount += ShelfOfBooks[0].NumberOfPages; //PageCount = 45

This is possible because the value returned by the operator is the actual referent, not a copy of the referent.

Generally we can say that references are preferred to pointers because they are safer (they can't be re-bound and don't require testing for NULL because they must refer to something). Also, they don't require explicit dereferencing, making code more intuitive.

What then of the pointers used in C++Builder's VCL? The reason behind the extensive use of pointers in the VCL is that the VCL is written in Object Pascal, which uses Object Pascal references. An Object Pascal reference is closer to a C++ pointer than a C++ reference. This has the side effect that, when the VCL is used with C++, pointers have to be used as replacements for Object Pascal references. This is because an Object Pascal reference (unlike a C++ reference) can be set to NULL and can be re-bound. In some cases it is possible to use reference parameters instead of pointer parameters, but because all VCL-based objects are dynamically allocated on free store and therefore are referred to through pointers, the pointers must be dereferenced first. Because the VCL relies on some of the features of Object Pascal references, pointers are used for object parameter passing and returning. Remember that a pointer parameter is passed by value, so the passed pointer will not be affected by the function. You can prevent modification of the object pointed to by using the const modifier.

Avoid Using Global Variables

Unless it is absolutely necessary, don't use global variables in your code. Apart from polluting the global namespace (and increasing the chance of a name collision), it increases the dependencies between translation units that use the variables. This makes code difficult to maintain and minimizes the ease with which translation units can be used in other programs. The fact that variables are declared elsewhere also makes code difficult to understand.

One of the first things any astute C++Builder programmer will notice is the global form pointers present in every form unit. This might give the impression that using global variables is OK; after all, C++Builder does it. However, C++Builder does this for a reason, which we will discuss at the end of this section. For now, we will examine some of the alternatives to declaring global variables.

Let's assume that global variables are a must. How can we use global variables without incurring some of the side effects that they produce? The answer is that we use something that acts like a global variable but is not one. We use a class with a member function that returns a value of or reference to (whichever is appropriate) a static variable that represents our global variable. Depending on the purpose of our global variables (for example, global to a program or global to a library), we may or may not need access to the variables through static member functions. In other words, it may be possible to instantiate an object of the class that contains the static variables when they are required. We consider first the case where we do require access to the static variables (representing our global variables) through static member functions. We commonly refer to this kind of class as a module.

With a module of global variables, we improve our representation of the variables by placing them into a class, making them private static variables, and using static getters and setters to access them (for more information, see Large-Scale C++ Software Design by Lakos, 1996). This prevents pollution of the global namespace and gives a certain degree of control over how the global variables are accessed. Typically, the class would be named Global. Hence, two global variables declared as

int Number;
double Average;

could be replaced by

class Global
{
 private:
  static int Number;
  static double Average;

  //PRIVATE CONSTRUCTOR
  Global(); //not implemented, instantiation not possible

 public:
  // SETTERS
  static void setNumber(int NewNumber) { Number = NewNumber; }
  static void setAverage(double NewAverage) { Average = NewAverage; }

  // GETTERS
  static int getNumber() { return Number; }
  static double getAverage() { return Average; }
};

Accessing Number is now done through Global::getNumber() and Global::setNumber(). Average is accessed similarly. The class Global is effectively a module that can be accessed throughout the program and does not need to be instantiated (because the member data and functions are static).

Often such an implementation is not required, and it is possible to create a class with a global point of access that is constructed only when first accessed. This has the benefit of allowing control over the order of initialization of the variables (objects must be constructed before first use). The method used is to place the required variables inside a class that cannot be directly instantiated, but accessed only through a static member function that returns a reference to the class. This ensures that the class containing the variables is constructed on first use and is constructed only once.

This approach is often referred to as the Singleton pattern (for more information, see Design Patterns: Elements of Reusable Object-Orientated Software by Gamma et al., 1995). Patterns are a way of representing recurring problems and their solutions in object-based programs. For more on patterns, see Chapter 4, "Advanced Programming with C++Builder."

The basic code required to create a Singleton (as such a class is commonly referred to) is as follows:

class Singleton
{
  public:
      static Singleton& Instance();

 protected:
      Singleton(); // Not Implemented, Instantiation not possible
};

An implementation of Instance is

Singleton& Singleton::Instance()
{
  static Singleton* NewSingleton = new Singleton();
  return *NewSingleton;
}

The initial call to Instance will create a new Singleton and return a reference to it. Subsequent calls will simply return a reference. However, the destructor of the Singleton will not be called; the object is simply abandoned on free store. If there is important processing that must be executed in the destructor, then the following implementation will ensure that the Singleton is destructed:

Singleton& Singleton::Instance()
{
  static Singleton NewSingleton;
  return NewSingleton;
}

This implementation causes its own problem. It is possible for another static object to access the Singleton after it has been destroyed. One solution to this problem is the nifty counter technique (for more information, see C++ FAQs Second Edition, Cline et al., 1999, and Large-Scale C++ Software Design, Lakos, 1996), in which a static counter is used to control when each object is created and destroyed. If you find the need for this technique, perhaps a re-think of the code would also be helpful. It may be that a slight redesign could remove the dependency.

It should now be clear that static variables are like global variables and can almost always be used in place of global variables. Remember, though, that ultimately global variables should be avoided.

Understand How C++Builder Uses Global Variables

What then of the global form pointer variables in C++Builder? Essentially, global form pointer variables are present to allow the use of non-modal forms. Such forms require a global point of access for as long as the form exists, and it is convenient for the IDE to automatically create one when the form is made. The default operation of the IDE is to add newly created forms to the auto-create list, which adds the line

Application->CreateForm(__classid(TFormX), &FormX);

(where X is a number) to the WinMain function in the project .cpp file. Modal forms do not require this, because the ShowModal() method returns after the forms are closed, making it possible to delete them in the same scope as they were created in. General guidelines on the use of forms can therefore be given.


Tip - You can uncheck the Auto Create Forms option on the Forms property page in the Tools, Environment Options menu to change the behavior of the IDE so that forms are not automatically added to the auto-create list. When this is done, forms are instead added to the available list.


First, determine if a form is to be a Modal form or a Non-Modal form.

If the form is Modal, then it is possible to create and destroy the form in the same scope. This being the case, the global form pointer variable is not required, and the form should not be auto-created. Remove the Application->CreateForm entry from WinMain either by deleting it or by removing it from the auto-create list on the Forms page in the Project, Options menu. Next, either delete or comment out the form pointer variable from the .h and .cpp files, and state explicitly in the header file that the form is Modal and should be used only with the ShowModal() method. That is, in the .cpp file remove

TFormX* FormX;

and from the .h file, remove

extern PACKAGE TFormX* FormX;

Add a comment such as the following:

// This form is MODAL and should only called with the ShowModal() method.

To use the form, simply write

TFormX* FormX = new TFormX(0);
try
{
  FormX->ShowModal();
}
__finally
{
  delete FormX;
}

Because you most likely do not want the form pointer to point elsewhere, you could declare the pointer as const:

TFormX* const FormX = new TFormX(0);
try
{
  FormX->ShowModal();
}
__finally
{
  delete FormX;
}

TFormX(this);
FormX->ShowModal();
delete FormX;

The use of a try/__finally block ensures that the code is exception-safe. An alternative to these examples is to use the Standard Library's auto_ptr class template:

auto_ptr<TFormX> FormX(new TFormX(0));
FormX->ShowModal();

Whichever technique you use, you are guaranteed that if the code terminates prematurely because an exception is thrown, FormX will be destructed automatically. With the first technique this happens in the __finally block; with the second it occurs when auto_ptr goes out of scope. The second technique can be further enhanced by making the auto_ptr const, since generally it is not required that the auto_ptr lose ownership of the pointer, as in the following code. (For more information, see Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions by Sutter, 2000.)

const auto_ptr<TFormX> FormX(new TFormX(0));
FormX->ShowModal();

Of particular note in the code snippets is that 0 (NULL) is passed as the argument to the AOwner parameter of FormX. This is because we handle the destruction of the form ourselves.


Tip - Using auto_ptr is an effective way of managing the memory of VCL-based objects. It is exception-safe and easy to use. For a VCL object that takes an owner parameter in its constructor, you can simply pass 0, because you know that the object will be deleted when the auto_ptr goes out of scope.


If the form is Non-Modal, you must decide only whether or not you want it auto-created. If you don't, you must ensure that it is removed from WinMain. When you want it created later, you can use the form's global pointer and the new operator. Show the form using the Show() method. Remember that you cannot delete Modal forms, because Show() returns when the form is shown, not when it is closed. Therefore, it may still be in use. For example, if the form is auto-created, write

FormX->Show();

Otherwise create and show it this way:

FormX = new TFormX(this);
FormX->Show();

It is important not to create the form again after it has been auto-created, because this will overwrite the reference to the auto-created form. This means that the auto-created instance can no longer be accessed by the application and can result in the application crashing when an attempt to dereference the global pointer is made. It is advisable therefore to check the value of the pointer for equality with NULL (0) before creation:

if(FormX == 0) FormX = new TFormX(this);
FormX->Show();

This is possible because the form pointer is a global variable that is guaranteed to be initialized to zero. Using this technique ensures that the global pointer will always point to a valid location and will not be overwritten.

As an aside to this topic, the practice of declaring variables or functions as static so that they have scope only within the translation unit in which they are declared is deprecated. Instead, such variables and functions should be placed in an unnamed namespace. (For more information, see ANSI/ISO C++ Professional Programmer's Handbook: The Complete Language by Kalev, 1999.)

Understand and Use const in Your Code

The const keyword should be used as a matter of course, not as an optional extra. Declaring a variable const allows attempted changes to the variable to be detected at compile time (resulting in an error) and also indicates the programmer's intention not to modify the given variable. Moreover, not using the const keyword indicates the programmer's intention to modify a given variable. The const keyword can be used in a variety of ways.

First, it can be used to declare a variable as a constant:

const double PI = 3.141592654;

This is the C++ way to declare constant variables. Do not use #define statements. Note that const variables must be initialized. The following shows the possible permutations for declaring const variables. Pointer and reference declarations are read from right to left, as the following examples show:

int Y = 12;
const int X = Y;    // X equals Y which equals 12, therefore X = 12
            // X cannot be changed, but Y can

// In the next declaration the pointer itself is constant

int* const P = &Y;   // The int pointed to by P, i.e. Y can be
            // changed through P but P itself cannot change

// The next two declarations are the same:

const int* P = &Y;   // The int pointed to by P, i.e.
int const* P = &Y;   // Y cannot be changed through P


// The next two declarations are the same:

const int* const P = &Y;  // Neither P, nor what it points to,
P
int const* const P = &Y;  // i.e. Y can be changed through P

// The next two declarations are the same:

const int& R = Y      // The int referred to by R, i.e. 
R
int const& R = Y      // Y cannot be changed through R 

After reviewing the previous examples, it is helpful to reiterate how const is used with pointer declarations. As stated previously, a pointer declaration is read from right to left, so that in int * const the const refers to the *. Hence, the pointer is constant, though the int it points to can be changed. With int const * the const refers to the int. In this case the int itself is constant, though the pointer to it is not. Finally, with int const * const, both the int and the * are constant. Also remember that int const and const int are the same, so const int * const is the same as int cosnt * const.

If you want to declare a literal string of chars, declare it as one of the following:

const char* const LiteralString = "Hello World";
char const * const LiteralString = "Hello World";

Both of the previous strings and the pointers to them are constant.

Function parameters should be declared as const in this fashion when it is appropriate, such as when the intention of the function is not to modify the argument that is passed to the function. For example, the following function states that it will not modify the arguments passed to it:

double GetAverage(const double* ArrayOfDouble, int LengthOfArray)
{
  double Sum = 0;

  for(int i=0; i<LengthOfArray; ++i)
  {
   Sum += ArrayOfDouble[i];
  }

  double Average = Sum/LengthOfArray;
  return Average;
}

Another way of thinking about this is to assume that if the const keyword is not used for a parameter, then it must be the intention of the function to modify that parameter's argument, unless the parameter is pass-by-value (a copy of the parameter is used, not the parameter itself). Notice that declaring int LengthOfArray as a const is inappropriate, because this is pass-by-value. LengthOfArray is a copy, and declaring it as a const has no effect on the argument passed to the function. Similarly, ArrayOfDouble is declared as follows:

const double* ArrayOfDouble

not

const double* const ArrayOfDouble

Because the pointer itself is a copy, only the data that it points to needs to be made const.

The return type of a function can also be const. Generally it is not appropriate to declare types returned by value as const, except in the case of requiring the call of a const-overloaded member function. Reference and pointer return types are suitable for returning as consts.

Member functions can be declared const. A const member function is one that does not modify the this object (*this). Hence, it can call other member functions inside its function body only if they are also const. To declare a member function const, place the const keyword at the end of the function declaration and in the function definition at the same place. Generally, all getter member functions should be const, because they do not modify *this. For example

class Book
{ 
  private:
      int NumberOfPages;
   public:
      Book();
      int GetNumberOfPages() const;
};

The definition of GetNumberOfPages() could be

int Book::GetNumberOfPages() const
{
  return NumberOfPages;
}

The final area in which const is commonly encountered is when operators are overloaded by a class and access to both const and non-const variables is required. For example, if a class ArrayOfBooks is created to contain Book objects, it is sensible to assume that the [ ] operator will be overloaded (so that the class acts like an array). However, the question of whether or not the [ ] operator will be used with const or non-const objects must be considered. The solution is to const-overload the operator, as the following code indicates:

class ArrayOfBooks
{
  public:
      Book&    operator[ ] (unsigned i);
      const Book& operator[ ] (unsigned i) const;
};

The ArrayOfBooks class can use the [ ] operator on both const and non-const Books. For example, if an ArrayOfBooks object is passed to a function by reference to const, it would be illegal for the array to be assigned to using the [ ] operator. This is because the value indexed by i would be a const reference, and the const state of the passed array would be preserved.

Remember, know what const is and use it whenever you can.

Be Familiar with the Principles of Exceptions

An exception is a mechanism for handling runtime errors in a program. There are several approaches that can be taken to handling runtime errors, such as returning error codes, setting global error flags, and exiting the program. In many circumstances, an exception is the only appropriate method that can be employed effectively, such as when an error occurs in a constructor. (For more information, see ANSI/ISO C++ Professional Programmer's Handbook: The Complete Language by Kalev, 1999.)

Exceptions will commonly be encountered in two forms in C++Builder programs: C++ exceptions and VCL exceptions. Generally the principles involved with both are the same, though there are some differences.

C++ uses three keywords to support exceptions; try, catch, and throw. C++Builder extends its exception support to include the __finally keyword.

The try, catch, and __finally keywords are used as headers to blocks of code (that is, code that is enclosed between braces). Also, for every try block there must always be one or more catch blocks or a single __finally block.

The try Keyword

The try keyword is used in one of two possible ways. The first and simplest is as a simple block header, to create a try block within a function. The second is as a function block header, to create a function try block, either by placing the try keyword in front of the function's first opening brace or, in the case of constructors, in front of the colon that signifies the start of the initializer list.


Note - C++Builder does not currently support function try blocks. However, because it makes a real difference only with constructors and even then has little impact on their use, it is unlikely that its omission will have any effect. For those who are interested, it will be supported in version 6 of the compiler.


The catch Keyword

Normally, at least one catch block will immediately follow any try block (or function try block). A catch block will always appear as the catch keyword followed by parentheses containing a single exception type specification with an optional variable name. Such a catch block (commonly referred to as an exception handler) can catch only an exception whose type exactly matches the exception type specified by the catch block. However, a catch block can be specified to catch all exceptions by using the catch all ellipses exception type specifier, catch(...).

A typical try/catch scenario is as follows:

try
{
  // Code that may throw an exception
}
catch(exception1& e)
{
  // Handler code for exception1 type exceptions
}
catch(exception2& e)
{
  // Handler code for exception2 type exceptions
}
catch(...)
{
  // Handler code for any exception not already caught
}

The __finally Keyword

The last of these, __finally, has been added to allow the possibility of performing cleanup operations or ensuring certain code is executed regardless of whether an exception is thrown. This works because code placed inside a __finally block will always execute, even when an exception is thrown in the corresponding try block. This allows code to be written that is exception-safe and will work properly in the presence of exceptions. A typical try/__finally scenario is

try
{
  // Code that may throw an exception
}
__finally
{
  // Code here is always executed, even if
  // an exception is thrown in the preceding
  // try block
}

It should be noted that try/catch and try/__finally constructs can be nested inside other try/catch and try/__finally constructs.

The throw Keyword

The throw keyword is used in one of two ways. The first is to throw (or rethrow) an exception, and the second is to allow the specification of the type of exceptions that a function may throw. In the first case (to throw or rethrow an exception), the throw keyword is followed optionally by parentheses containing a single exception variable (often an object) or simply the single exception variable after a space, similar to a return statement. When no such exception variable is used, the throw keyword stands on its own. Then its behavior depends on its placement. When placed inside a catch block, the throw statement rethrows the exception currently being handled. When placed elsewhere, such as when there is no exception to rethrow, it causes terminate() to be called, ultimately ending the program. It is not possible to use throw to rethrow an exception in VCL code. The second use of the throw keyword is to allow the specification of the exceptions that a function may throw. The syntax for the keyword is

throw(<exception_type_list>)

The exception_type_list is optional and when excluded indicates that the function will not throw any exceptions. When included, it takes the form of one or more exception types separated by commas. The exception types listed are the only exceptions the function may throw.

Unhandled and Unexpected Exceptions

In addition to the three keywords described, C++ offers mechanisms to deal with thrown exceptions that are not handled by the program and exceptions that are thrown but are not expected. This might include an exception that is thrown inside a function with an incompatible exception specification.

When an exception is thrown but not handled, terminate() is called. This calls the default terminate handler function, which by default calls abort(). This default behavior should be avoided because abort() does not ensure that local object destructors are called. To prevent terminate() being called as a result of an uncaught exception, the entire program can be wrapped inside a try/catch(...) block in WinMain() (or main() for command-line programs). This ensures that any exception will eventually be caught. If terminate() is called, you can modify its default behavior by specifying your own terminate handler function. Simply pass the name of your terminate handler function as an argument to the std::set_ terminate() function. The <stdexcept> header file must be included. For example, given a function declared as

void TerminateHandler();

The code required to ensure that this handler is called in place of the basic terminate() handler is

#include <stdexcept>

std::set_terminate(TerminateHandler);

When an exception is thrown that is not expected, then unexpected() is called. Its default behavior is to call terminate(). Again, the opportunity exists to define your own function to handle this occurrence. To do so, call std::set_unexpected(), passing the function handler name as an argument. The <stdexcept> header file must be included.

Using Exceptions

This brings the discussion to consideration of the exceptions that can and should be thrown by a function and where such exceptions should be caught. This should be decided when you are designing your code, not after it has already been written. To this end, you must consider several things when you write a piece of code. Some of the topics are very complex, and it is beyond the scope of this book to cover all the issues involved. Instead, check the "Further Reading" section at the end of this chapter for more information.

You must consider if the code you have written could throw one or more exceptions. If so, you must then consider if it is appropriate to catch one or more of the exceptions in the current scope or let one or more of them propagate to an exception handler outside the current scope. If you do not want one or more of the exceptions to propagate outside the current scope, then you must place the code in a try block and follow it with the one or more appropriate catch blocks to catch any desired exceptions (or all exceptions, using a catch-all block). To this end, you should be aware of the exceptions built into the language itself, the C++ Standard Library, and the VCL and be aware of when they may be thrown. For example, if new fails to allocate enough memory, std::bad_alloc is thrown.

Throw an exception in a function only when it is appropriate to do so, when the function cannot meet its promise. (See the section "The Use of Comments," earlier in this chapter, for a discussion of a function's promise. Also see C++ FAQs, Second Edition, Cline et al., 1999.)

You should catch an exception only when you know what to do with it, and you should always catch an exception by reference. (For more information, see More Effective C++: 35 New Ways to Improve Your Programs and Designs by Meyers, 1996.) VCL exceptions cannot be caught by value. Also, it may not be possible to fully recover from an exception, in which case the handler should perform any possible cleanup and then rethrow the exception.

You should understand when and how to use exception specifications for functions and be wary of the possibility of writing an incorrect specification. This will result in unexpected() being called if an unspecified exception is thrown inside a function and it is not handled within that function.

You should ensure that you write exception-safe code that works properly in the presence of exceptions. For example, simple code such as this is not exception safe:

TFormX* const FormX = new TFormX(0);
FormX->ShowModal();
delete FormX;

If an exception is thrown between the creation and deletion of the form, the form will never be deleted, so the code does not work properly in the presence of exceptions. For an exception-safe alternative, see the section "Avoid Global Variables," earlier in this chapter.

If you are writing container classes, endeavor to write code that is exception-neutral—code that propagates all exceptions to the caller of the function that contains the code. (For more information, see Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions by Sutter, 2000.)

Never throw an exception from a destructor, because the destructor may have been called as a result of stack unwinding after a previous exception was called. This calls terminate(). Destructors should have an exception specification of throw().

A Final Note on Exceptions

Finally, you should appreciate the differences between VCL and C++ exceptions. VCL exceptions allow operating system exceptions to be handled as well as exceptions generated from within the program. Such exceptions must be caught by reference. VCL exceptions generated from within the program cannot be caught by value. An advantage of VCL exceptions is that they can be thrown and caught within the IDE.

Use new and delete to Manage Memory

The VCL requires that all classes that inherit from TObject are created dynamically in free store. Free store is often referred to as the heap, but free store is the correct term when applied to memory allocated and deallocated by new and delete. The term heap should be reserved for the memory allocated and deallocated by malloc() and free(). (For more information, see Exceptional C++: 47 Engineering Puzzles, Programming Problems, and Solutions by Sutter, 2000.) This means a lot of calls to new and delete in C++Builder programs, so it is important to understand a few things about how new and delete work.


Caution - A Non-Plain Old Data (Non-POD) object is essentially any but the most trivial of classes. Such objects must have their memory allocated by using new; the C equivalent malloc() will not suffice (its behavior is undefined) and be subsequently deallocated with delete, not free(). The new and delete operators ensure that, in addition to the allocation/deallocation of memory, the object's constructor and destructor, respectively, are called.

The new operator also returns a pointer that is suitable to the object created, not merely a void pointer that must be cast to the required type. new and delete call operator new/operator delete, respectively, to allocate/deallocate memory, and these can be overloaded for specific classes. This allows the customization of memory allocation/deallocation behavior. This is not possible with malloc() and free().

(For more information, see ANSI/ISO C++ Professional Programmer's Handbook: The Complete Language by Kalev, 1999.)


A successful call to new allocates sufficient memory in free store (using operator new) calls the object's constructor and returns a pointer of the type pointer-to-the-object-type-created. A correctly initialized object is the result. Subsequently calling delete calls the object's destructor and deallocates the memory obtained previously by calling new.


Caution - Never call a VCL object's Free() method to destroy a VCL object. Always use delete. This ensures that the object's destructor is called and that the memory allocated previously with new is freed. Free() does not guarantee this, and it is bad practice to use it.


If the call to new is unsuccessful, a std::bad_alloc exception is thrown. Note that the bad_alloc exception is defined in the standard library file <new>. Hence, you must include #include <new> in your program, and it is in the std namespace. It does not return NULL. Therefore, you should not check the return pointer for equality with NULL. The program should be prepared to catch the std::bad_alloc exception and, if the function that calls new does not catch the exception, it should pass the exception outside the function, so that calling code has the opportunity to catch it. Either of the following would be appropriate:

void CreateObject(TMyObject* MyObject) throw()
{
  try
  {
   MyObject = new TMyObject();
  }
  catch(std::bad_alloc)
  {
   //Print a message "Not enough memory for MyObject";
   // Deal with the problem
   // or exit gracefully
  }
}

or

void CreateObject(TMyObject* MyObject) throw(std::bad_alloc)
{
  MyObject = new TMyObject();
}

The use of exceptions allows the code that handles the error to be centralized, which leads to safer code that is more intuitive. The throw keyword added to the function header is called an exception specification. The effect of its inclusion in the function header is to specify which exceptions the function may throw. For more explanation refer to the section "Be Familiar with the Principles of Exceptions," earlier in this chapter. In the case of the first CreateObject() function, a throw() exception specifier is used to indicate that no exception will be thrown by the function. This is acceptable, because the only exception that may be thrown, std::bad_alloc, is caught and dealt with by the function itself. In the case of the second implementation of CreateObject(), the exception specifier throw(std::bad_alloc) is used to indicate that the only exception that the function can throw is std::bad_alloc. This should be caught and handled by one of the calling routines.

There is also the possibility of writing your own out-of-memory function handler to deal with failed memory allocation. To set a function as a handler for out-of-memory conditions when using new, call the set_new_handler() function (also defined in <new>), passing as a parameter the name of the function you will use as the out-of-memory handler. For example, if you write a function (non-member or static member) called OutOfMemory to handle such occurrences, the necessary code would be

#include <new>

void OutOfMemory()
{
  // Try to free some memory
  // if there is now enough memory then this
  // function will NOT be called next time
  // else either install a new handler or throw an exception
}

// Somewhere in the main code, near the start write:
std::set_new_handler(OutOfMemory);

This code requires some explanation, because the sequence of events that occurs when new fails dictates how the OutOfMemory function should be written. If new fails to allocate enough memory, then OutOfMemory is called. OutOfMemory tries to free some memory (how this is done will be discussed later); new will then try again to allocate the required memory. If it is successful, we are finished. If it is unsuccessful, the process just described will be repeated. In fact, it will repeat infinitely until either enough memory is allocated or the OutOfMemory function terminates the process.

To terminate the process, the OutOfMemory function can do several things. It can throw an exception (such as std::bad_alloc()), it can install a different memory handler that can then try to make more memory available, it can assign NULL to set_new_handler (std::set_new_handler(0)), or it can exit the program (not recommended). If a new handler is installed, then this series of events will occur for the new handler (which is called on the subsequent failed attempt). If the handler is set to NULL (0), then no handler will be called, and the exception std::bad_alloc() will be thrown.

Making more memory available is dependent on the design of the program and where the memory shortage arises from. If the program keeps a lot of memory tied up for performance reasons but does not always require it to be available at all times, then such memory can be freed if a shortage occurs. Identifying such memory is the difficult part. If there is no such memory usage in the program, then the shortage will be a result of factors external to the program, such as other memory-intensive software or physical limitations. There is nothing that can be done about physical limitations, but it is possible to warn the user of a memory shortage so that memory-intensive software can be shut down, thereby freeing additional memory.

The trick is to give an advance warning before all the memory is used up. One approach is to pre-allocate a quantity of memory at the beginning of the program. If new fails to allocate enough memory, then this memory can be freed. The user is warned that memory is low and told to try to free more memory for the application. Assuming that the pre-allocated block was large enough, the program should be able to continue operating as normal if the user has freed additional memory. This preemptive approach is simple to implement and reasonably effective. There are other approaches, and the reader is directed to the books in the "Further Reading" section for more information.

It is important to note that if you want to allocate raw memory only, then operator new and operator delete should be used instead of the new and delete operators. (For more information, see More Effective C++: 35 New Ways to Improve Your Programs and Designs by Meyers, 1996.) This is useful for situations in which, for example, a structure needs to be allocated dynamically, and the size of the structure is determined through a function call before the dynamic allocation. This is a common occurrence in Win32 API programming:

DWORD StructureSize = APIFunctionToGetSize(SomeParameter);

WIN32STRUCTURE* PointerToStructure;
PointerToStructure = static_cast<WIN32STRUCTURE*>(operator new(StructureSize));
// Do something with the structure
operator delete(PointerToStructure);

It is clear that the use of malloc() and free() should not be required.

Finally, we will discuss the use of new and delete in dynamically allocating and deallocating arrays. Arrays are allocated and deallocated using operator new[ ] and operator delete[ ], respectively. They are separate operators from operator new and operator delete. When new is used to create an array of objects, it first allocates the memory for the objects (using operator new[ ]) and then each object is initialized by calling its default constructor. Deleting an array using delete performs the opposite task: It calls the destructor for each object and then deallocates the memory (using operator delete[ ]) for the array. So that delete knows to call operator delete[ ] instead of operator delete, a [ ] is placed between the delete keyword and the pointer to the array to be deleted:

delete [ ] SomeArray;

Allocating a single-dimensional array is straightforward. The following format is used:

TBook* ArrayOfBooks = new TBook[NumberOfBooks];

Deleting such an array is also straightforward. However, remember that the correct form of delete must be used—delete [ ]. For example

delete [ ] ArrayOfBooks;

Remember that [ ] tells the compiler that the pointer is to an array, as opposed to simply a pointer to a single element of a given type. If an array is to be deleted, it is essential that delete [ ] be used, not delete. If delete is used erroneously, then at best only the first element of the array will be deleted. We know that when an array of objects is created, the default constructor is used. This means that you will want to ensure that you have defined the default constructor to suit your needs. Remember that a compiler-generated default constructor does not initialize the classes' data members. Also, you will probably want to overload the assignment operator (=) so that you can safely assign object values to the array objects. A two- dimensional array can be created using code such as the following:

TBook** ShelvesOfBooks = new TBook*[NumberOfShelves];

for(int i=0; i<NumberOfShelves; ++i)
{ 
  ShelvesOfBooks[i] = new TBook[NumberOfBooks];
}

To delete such an array use the following:

for(int i=0; i<NumberofShelves; ++i)
{
  delete [ ] ShelvesOfBooks[i];
}

delete [ ] ShelvesOfBooks;

One thing remains unsaid: If you want to have an array of objects, a better approach is to create a vector of objects using the vector template from the STL. It allows any constructor to be used and also handles memory allocation and deallocation automatically. It will also reallocate memory if there is a memory shortage. This means that the use of the C library function realloc() is also no longer required. For more information on the vector template class, refer to the "Introduction to the Standard C++ Library and Templates" section in Chapter 4.

Placement new (allocation at a predetermined memory location) and nothrow new (does not throw an exception on failure, returns NULL instead) have not been discussed, because they are beyond the scope of this section. However, if more information is required on either of these, please refer to the "Further Reading" section.

Understand and Use C++-Style Casts

There are four C++ casts. They are outlined in Table 3.1.

Table 3.1 C++-Style Casts

Cast

General Purpose

static_cast<T>(exp)

Used to perform casts such as an int to a double. T and exp may be a pointer, a reference, an arithmetic type (such as int), or an enum type. You cannot cast from one type to another, such as from a pointer to an arithmetic.

dynamic_cast<T>(exp)

Used to perform casting down or across an inheritance hierarchy. For example, if class X inherits from class O, then a pointer to class O can be cast to a pointer to class X, provided the conversion is valid. T may be a pointer or a reference to a defined class type or void*. exp may be a pointer or a reference. For a conversion from a base class to a derived class to be possible, the base class must contain at least one virtual function; in other words, it must be polymorphic. One important feature of dynamic_cast is that if a conversion between pointers is not possible, a NULL pointer is returned; if a conversion between references is not possible, a std::bad_cast exception is thrown (include the header file <typeinfo>). As a result, the conversion can be checked for success.

const_cast<T>(exp)

This is the only cast that can affect the const or volatile nature of an expression. It can be either cast off or cast on. This is the only thing const_cast is used for. For example, if you want to pass a pointer to const data to a functionthat only takes a pointer to non-const data, and you know the data will not be modified, you could pass the pointer by const_casting it. T and exp must be of the same type except for their const or volatile factors.

reinterpret_cast<T>(exp)

Used to perform unsafe orimplmentation-dependent casts. This cast should be used only when nothing else will do. This is because it allows you to re-interpret the expression as a completely different type, such as to cast a float* to an int*. It is commonly used to cast between function pointers. If you find yourself needing to use reinterpret_cast, decide carefully if the approach you are taking is the right one, and remember to document clearly your intention (and possibly your reasons for this approach). T must be a pointer, a reference, an arithmetic type, a pointer to a function, or a pointer to a member function. A pointer can be cast to an integral type and vice versa.


The casts most likely to be of use are static_cast (for trivial type conversions such as int to double) and dynamic_cast.

An example of using static_cast can be found in the last line of the following code:

int Sum = 0;
int* Numbers = new int[20];

for(int i=0; i<20; ++i)
{
  Numbers[i] = i*i;
  Sum += Numbers[i];
}

double Average = static_cast<double>(Sum)/20;

The astute among you will recognize this as the code from Listing 3.4 earlier in this chapter.

One of the times when dynamic_cast is commonly used in C++Builder is to dynamic_cast TObject* Sender or TComponent* Owner, to ensure that Sender or Owner is of a desired class, such as TForm. For example, if a component is placed on a form, it may be necessary to distinguish if it was placed directly or was perhaps placed on a Panel component. To carry out such a test, the following code is required:

TForm* OwnerForm = dynamic_cast<TForm*>(Owner);
if(OwnerForm)
{
  //Perform processing since OwnerForm != NULL, i.e. 0
}

First a pointer of the required type is declared, and then it is set equal to the result of the dynamic_cast. If the cast is unsuccessful, the pointer will point to the required type and can be used for accessing that type. If it fails, it will point to NULL, and hence can be used to evaluate a Boolean expression. Sender can be similarly used. The situations that require such casting are many and varied. What is important is to understand what it is that you want to achieve and make your intention and reasoning clear.

Each of the C++ casts performs a specific task and should be restricted for use only where appropriate. The C++ casts are also easily seen in code, making it more readable.

Know When to Use the Preprocessor

It is not appropriate to use the preprocessor for defining constants or for creating function macros. Instead, you should use const variables or enum types for constants and use an inline function (or inline template function) to replace a function macro. Consider also that a function macro may not be appropriate anyway (in which case the inline equivalent would not be required).

For example, the constant þ can be defined as

const double PI = 3.141592654;

If you wanted to place this inside a class definition, then you would write

class Circle
{
  public:
     static const double PI; // This is only a declaration
};

In the implementation (*.cpp) file, you would define and initialize the constant by writing

const double Circle::PI = 3.141592654; 
// This is the constant definition
                    // and initialization

Note that the class constant is made static so that only one copy of the constant exists for the class. Also notice that the constant is initialized in the implementation file (typically after the include directive for the header file that contains the class definition). The exception to this is the initialization of integral types, char, short, long, unsigned, and int. These can be initialized directly in the class definition. When a group of related constants is required, an enum is a sensible choice:

enum LanguagesSupported { English, Chinese, Japanese, French };

Sometimes an enum is used to declare an integer constant on its own:

enum { LENGTH = 255 };

Such declarations are sometimes seen inside class definitions. A static const variable declaration (like that for PI) is a more correct approach.

Replacing a function macro is also easily achieved. Given the macro

#define cubeX(x) ( (x)*(x)*(x) )

the following inline function equivalent can be written:

inline double cubeX(double x) { return x*x*x; }

Notice that this function takes a double as an argument. If an int were passed as a parameter, it would have to be cast to a double. Because we want the behavior of the function to be similar to that of the macro, we should avoid this necessity. This can be achieved in one of two ways: Either overload the function or make it a function template. In this case, overloading the function is the better of the two choices, because a function template would imply that the function could be used for classes as well, which would most likely be inappropriate. Therefore, an int version of the inline function could be written as

inline int cubeX(int x) { return x*x*x; }

Generally, we want to avoid using #define for constants and function macros. #define should be used when writing include guards. Remember that include guards are written in the header file to ensure that a header already included is not included again. For example, a typical header file in C++Builder will look like this:

#ifndef Unit1H // Is Unit1H not already defined?
#define Unit1H // If not then we reach this line and define it

// Header file code placed here...

#endif     // End of if Unit1H not defined

This code ensures that the code between #ifndef and #endif will be included only once. It is a good idea to follow some convention when choosing suitable defines for header files. C++Builder uses an uppercase H after the header filename. If you write your own translation units, you should follow this convention. Of course, you can use a different naming convention, such as pre-pending INCLUDED_ to the header filename, but you should be consistent throughout a project. Using include guards prevents a header file from being included more than once, but it must still be processed to see if it is to be included.


Tip - When you follow the IDE naming convention for include guards (appending an 'H' to the end of the header filename), the IDE treats the translation unit as a set, and it will appear as such in the Project Manager. If you do not want your .cpp and .h files to be treated in this way, do not use IDE-style include guards.


It has been shown that for very large projects (or more generally, projects with large, dense include graphs), this can have a significant effect on compile times. (For more information, see Large-Scale C++ Software Design by Lakos, 1996.) Therefore, it is worth wrapping all include statements in an include guard to prevent the unnecessary inclusion of a file that has been already defined. For example, if Unit1 from the previous code snippet also included ModalUnit1, ModalUnit2, and ModalUnit3, which are dialog forms used by other parts of the program, their include statements could be wrapped inside an include guard as follows:

#ifndef Unit1H // Is Unit1H not already defined?
#define Unit1H // If not then we reach this line and define it

#ifndef ModalUnit1H    // Is ModalUnit1H not already defined?
#include "ModalUnit1.h"  // No then include it
#endif          // End of if Unit1H not defined

#ifndef ModalUnit2H
#include "ModalUnit2.h"
#endif

#ifndef ModalUnit3H
#include "ModalUnit3.h"
#endif

// Header file code placed here...

#endif     // End of if Unit1H not defined

This is not pretty but it is effective. Remember that you must ensure that the names you define for include guards must not match any name that appears elsewhere in your program. The define statement will ensure that it is replaced with nothing, which could cause havoc. That is why a naming convention must be agreed upon and adhered to.


Tip - Note that the Project Manager in C++Builder 5 has been improved to include an expandable list of header file dependencies for each source file included in a project. Simply click on the node beside the source filename to either expand or collapse the list. Note that the header file dependency lists are based on the source file's .obj file, hence the file must be compiled at least once to use this feature. Also note that the list could be out of date if changes are made without recompilation.


Know when using the preprocessor will benefit the program and when it won't. Use it carefully and only when necessary.

Learn About and Use the C++ Standard Library

The C++ Standard Library, including the Standard Template Library (STL), is a constituent part of ANSI/ISO C++, just as the definition for bool is. You can save a lot of unnecessary coding by learning to use its features in your programs. The Standard Library has an advantage over homegrown code in that it has been thoroughly tested and is fast, and it is the standard, so portability is a big bonus. Standard Library features are summarized in the following list:

  • Exceptions, such as bad_alloc, bad_cast, bad_typeid, and bad_exception

  • Utilities, such as min(), max(), auto_ptr<T>, and numeric_limits<T>

  • Input and output streams, such as istream and ostream

  • Containers, such as vector<T>

  • Algorithms, such as sort()

  • Function objects (functors), such as equal_to<T>()

  • Iterators

  • Strings, such as string

  • Numerics, such as complex<T>

  • Special containers, such as queue<T> and stack<T>

  • Internationalization support

Nearly everything in the Standard Library is a template, and most of the library consists of the STL, so it is very flexible. For example, the vector template class can be used to store any kind of data of the same type. As a result, it is a direct replacement for arrays in C++ and should be used in preference to arrays whenever possible. For more information about the STL, refer to the "Introduction to the Standard C++ Library and Templates" section in Chapter 4.

InformIT Promotional Mailings & Special Offers

I would like to receive exclusive offers and hear about products from InformIT and its family of brands. I can unsubscribe at any time.

Overview


Pearson Education, Inc., 221 River Street, Hoboken, New Jersey 07030, (Pearson) presents this site to provide information about products and services that can be purchased through this site.

This privacy notice provides an overview of our commitment to privacy and describes how we collect, protect, use and share personal information collected through this site. Please note that other Pearson websites and online products and services have their own separate privacy policies.

Collection and Use of Information


To conduct business and deliver products and services, Pearson collects and uses personal information in several ways in connection with this site, including:

Questions and Inquiries

For inquiries and questions, we collect the inquiry or question, together with name, contact details (email address, phone number and mailing address) and any other additional information voluntarily submitted to us through a Contact Us form or an email. We use this information to address the inquiry and respond to the question.

Online Store

For orders and purchases placed through our online store on this site, we collect order details, name, institution name and address (if applicable), email address, phone number, shipping and billing addresses, credit/debit card information, shipping options and any instructions. We use this information to complete transactions, fulfill orders, communicate with individuals placing orders or visiting the online store, and for related purposes.

Surveys

Pearson may offer opportunities to provide feedback or participate in surveys, including surveys evaluating Pearson products, services or sites. Participation is voluntary. Pearson collects information requested in the survey questions and uses the information to evaluate, support, maintain and improve products, services or sites, develop new products and services, conduct educational research and for other purposes specified in the survey.

Contests and Drawings

Occasionally, we may sponsor a contest or drawing. Participation is optional. Pearson collects name, contact information and other information specified on the entry form for the contest or drawing to conduct the contest or drawing. Pearson may collect additional personal information from the winners of a contest or drawing in order to award the prize and for tax reporting purposes, as required by law.

Newsletters

If you have elected to receive email newsletters or promotional mailings and special offers but want to unsubscribe, simply email information@informit.com.

Service Announcements

On rare occasions it is necessary to send out a strictly service related announcement. For instance, if our service is temporarily suspended for maintenance we might send users an email. Generally, users may not opt-out of these communications, though they can deactivate their account information. However, these communications are not promotional in nature.

Customer Service

We communicate with users on a regular basis to provide requested services and in regard to issues relating to their account we reply via email or phone in accordance with the users' wishes when a user submits their information through our Contact Us form.

Other Collection and Use of Information


Application and System Logs

Pearson automatically collects log data to help ensure the delivery, availability and security of this site. Log data may include technical information about how a user or visitor connected to this site, such as browser type, type of computer/device, operating system, internet service provider and IP address. We use this information for support purposes and to monitor the health of the site, identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents and appropriately scale computing resources.

Web Analytics

Pearson may use third party web trend analytical services, including Google Analytics, to collect visitor information, such as IP addresses, browser types, referring pages, pages visited and time spent on a particular site. While these analytical services collect and report information on an anonymous basis, they may use cookies to gather web trend information. The information gathered may enable Pearson (but not the third party web trend services) to link information with application and system log data. Pearson uses this information for system administration and to identify problems, improve service, detect unauthorized access and fraudulent activity, prevent and respond to security incidents, appropriately scale computing resources and otherwise support and deliver this site and its services.

Cookies and Related Technologies

This site uses cookies and similar technologies to personalize content, measure traffic patterns, control security, track use and access of information on this site, and provide interest-based messages and advertising. Users can manage and block the use of cookies through their browser. Disabling or blocking certain cookies may limit the functionality of this site.

Do Not Track

This site currently does not respond to Do Not Track signals.

Security


Pearson uses appropriate physical, administrative and technical security measures to protect personal information from unauthorized access, use and disclosure.

Children


This site is not directed to children under the age of 13.

Marketing


Pearson may send or direct marketing communications to users, provided that

  • Pearson will not use personal information collected or processed as a K-12 school service provider for the purpose of directed or targeted advertising.
  • Such marketing is consistent with applicable law and Pearson's legal obligations.
  • Pearson will not knowingly direct or send marketing communications to an individual who has expressed a preference not to receive marketing.
  • Where required by applicable law, express or implied consent to marketing exists and has not been withdrawn.

Pearson may provide personal information to a third party service provider on a restricted basis to provide marketing solely on behalf of Pearson or an affiliate or customer for whom Pearson is a service provider. Marketing preferences may be changed at any time.

Correcting/Updating Personal Information


If a user's personally identifiable information changes (such as your postal address or email address), we provide a way to correct or update that user's personal data provided to us. This can be done on the Account page. If a user no longer desires our service and desires to delete his or her account, please contact us at customer-service@informit.com and we will process the deletion of a user's account.

Choice/Opt-out


Users can always make an informed choice as to whether they should proceed with certain services offered by InformIT. If you choose to remove yourself from our mailing list(s) simply visit the following page and uncheck any communication you no longer want to receive: www.informit.com/u.aspx.

Sale of Personal Information


Pearson does not rent or sell personal information in exchange for any payment of money.

While Pearson does not sell personal information, as defined in Nevada law, Nevada residents may email a request for no sale of their personal information to NevadaDesignatedRequest@pearson.com.

Supplemental Privacy Statement for California Residents


California residents should read our Supplemental privacy statement for California residents in conjunction with this Privacy Notice. The Supplemental privacy statement for California residents explains Pearson's commitment to comply with California law and applies to personal information of California residents collected in connection with this site and the Services.

Sharing and Disclosure


Pearson may disclose personal information, as follows:

  • As required by law.
  • With the consent of the individual (or their parent, if the individual is a minor)
  • In response to a subpoena, court order or legal process, to the extent permitted or required by law
  • To protect the security and safety of individuals, data, assets and systems, consistent with applicable law
  • In connection the sale, joint venture or other transfer of some or all of its company or assets, subject to the provisions of this Privacy Notice
  • To investigate or address actual or suspected fraud or other illegal activities
  • To exercise its legal rights, including enforcement of the Terms of Use for this site or another contract
  • To affiliated Pearson companies and other companies and organizations who perform work for Pearson and are obligated to protect the privacy of personal information consistent with this Privacy Notice
  • To a school, organization, company or government agency, where Pearson collects or processes the personal information in a school setting or on behalf of such organization, company or government agency.

Links


This web site contains links to other sites. Please be aware that we are not responsible for the privacy practices of such other sites. We encourage our users to be aware when they leave our site and to read the privacy statements of each and every web site that collects Personal Information. This privacy statement applies solely to information collected by this web site.

Requests and Contact


Please contact us about this Privacy Notice or if you have any requests or questions relating to the privacy of your personal information.

Changes to this Privacy Notice


We may revise this Privacy Notice through an updated posting. We will identify the effective date of the revision in the posting. Often, updates are made to provide greater clarity or to comply with changes in regulatory requirements. If the updates involve material changes to the collection, protection, use or disclosure of Personal Information, Pearson will provide notice of the change through a conspicuous notice on this site or other appropriate way. Continued use of the site after the effective date of a posted revision evidences acceptance. Please contact us if you have questions or concerns about the Privacy Notice or any objection to any revisions.

Last Update: November 17, 2020