Home > Articles > Programming > C/C++

Programming in C++Builder

  • Print
  • + Share This
This chapter introduces important concepts concerned with how code is written in C++Builder. Authors Jamie Allsop, Daniel Butterfield, Jarrod Hollingworth, and Bob Swart discuss how the readability of code may be improved. They consider a variety of approaches to coding style in an effort to provide some insight into what may be the best style for you, and they offer suggestions as to why certain styles are possibly more effective than others as well as warnings on the application of certain styles. This chapter also covers a variety of topics concerning concerning the writing of C++ code in C++Builder applications. From the use of new and delete to the use of const in programs, learn the areas of coding that are often misunderstood or misused.
This chapter is excerpted from C++Builder!™ 5 Developer's Guide.

In This Chapter

  • Coding Style to Improve Readability

  • Better Programming Practices in C++Builder

  • Further Reading

This chapter introduces important concepts concerned with how code is written in C++Builder.

The first section concentrates on how the readability of code may be improved. Optimizing the readability of code is very important. More readable code is easier to understand and maintain, and this can have a big effect on the cost of a software project. It is more than just making code look pretty on a page; it is about augmenting the code's description such that its purpose, intent, and behavior are adequately reflected.

Adopting a coding style and applying it consistently is probably the easiest way to achieve more readable code. However, some styles offer greater improvement than others, and this section looks at a variety of approaches to several considerations in an effort to give the reader some insight into which styles they should adopt. More importantly, it offers suggestions as to why certain styles are possibly more effective than others as well as warnings on the application of certain styles. Some techniques in moderation are very effective but when overused will decrease readability. Situations such as this are highlighted. The fundamental elements of coding style are examined in turn, and guidelines are presented on how each element can affect readability, along with suggestions on how each element can be used to best effect. Where appropriate, effective use of the IDE is also covered.

The second section covers a variety of topics concerning the writing of C++ code in C++Builder applications. A wide range of topics is covered, from the use of new and delete to the use of const in programs. The main purpose of the section is to highlight areas of coding that are often misunderstood or misused. Often, certain concepts are not fully appreciated, particularly by programmers whose background is not in C++. However, the main target audience is those whose background is in C++. They will find explanations of specific issues concerning the use of C++ in C++Builder and should therefore benefit from the topics covered. Some topics begin with more straightforward material before moving the discussion to advanced concepts. Also, many of the topics address issues that arise as a result of the Object Pascal heritage of the VCL (Visual Component Library).

Finally, the last section, "Further Reading," lists the references that appear throughout the chapter, along with a brief description of the reference itself. You are encouraged to seek out these references; reading them should prove very beneficial.

Coding Style to Improve Readability

This section looks at some of the issues in improving the readability of code and offers possible methods of achieving this. Regardless of which styles and methods are chosen, the one thing that should be remembered is that you must be consistent. Consistency is very important (note that code in this section is written in different styles to illustrate points in the text).

The Use of Short and Simple Code

It may be obvious, but always try to keep any given block of code short and simple. This achieves two things.

First, it means that your code is ultimately the culmination of many smaller pieces of code, each of which serves a specific and understandable role. This means that at different parts of the code the complexity of the code is governed only by the level of abstraction, not by the amount of code present. Consider the two functions in Listing 3.1.

Listing 3.1 Code Complexity and Level of Abstraction

#include <vector>

double GetMaximumValue(const std::vector<double>& Vector) 
            throw(std::out_of_range)
{
  double Maximum = Vector.at(0);
  
  for(int i=0; i<Vector.size(); ++i)
  {
   if(Vector[i] > Maximum)
   {
     Maximum = Vector[i];
   }
  }

  return Maximum;
}

void NormalizeVector(std::vector<double>& Vector)
{
  if(!Vector.empty())
  {
   double Maximum = GetMaximumValue(Vector);
 
   for(int i=0; i<Vector.size(); ++i)
   {
     Vector[i] -= Maximum;
   }
  }  

}

Both of the above functions are similar in terms of the complexity of the code that they contain, but the second function, NormalizeVector, performs in total a more complex task, because the level of abstraction is higher.


Tip - Use the pre-increment operator instead of the post-increment operator—for example, in Listing 3.1, ++i is used in preference to i++. Using either of the increment operators will increment i, but the post-increment version will return the old value of i. Since this is not required, the extra processing time is wasted. Typically, the post-increment operator is implemented in terms of the pre-increment operator. The pre-increment operator is therefore faster than the post-increment operator.

For built-in types such as int, there is little difference in speed, but for user-defined types this may not be the case. Regardless, if the post-increment operator is repeatedly used unnecessarily, there will be a cost in terms of performance.


Second, it means that when you are reading code you are never too far away from local variable declarations and function parameters (which contain type information). To this end, if you find yourself writing large pieces of code that perform several tasks, consider how the code could be separated into smaller conceptual blocks and rewritten accordingly.

The Use of Code Layout

The easiest way to improve the layout of your code is to make sure that braces ({}) are placed on their own line and that matching pairs line up. Code inside braces should then be indented by a predetermined amount that is used consistently throughout the program. Typically, two to four spaces are used as a suitable indent, though some use more and some use only one. (Beware of too many spaces; this can actually degrade readability.)


Tip - Make sure the Use Tab Character setting on the General tab in Tools, Editor Options is unchecked. This ensures that spaces are inserted for tabs. In general, avoid using Tab; use spaces instead.


Why is this so helpful? First, it allows fast visual scanning of the code. The code can be broken quickly into its constituent functional blocks, and each block's nature can be seen from the line starting the block. Second, the scope of the code is very clear. It is obvious which variables are in or out of scope at any given time, and this can be helpful in tracking down problem code. This is an important feature of this approach. Remember that the scope of any given block includes the block header statement (such as a for statement or a function header), and the functional part of the block is the code contained by the braces. Aligning the braces with the block header statement and indenting the functional code show this logical relationship explicitly.

The reason this layout style is so effective is that it is easier to match a pair of braces than it is to match an end brace with a keyword (or other permutations), due to the lack of symbolic similarity. It is also easy to maintain, because each opening brace must have a corresponding ending brace in the same column.


Tip - The IDE allows you to indent and unindent selected blocks of code using, respectively, CTRL+SHIFT+I and CTRL+SHIFT+U. The number of spaces that the editor indents or unindents the selected code is determined by the value of the Block indent setting on the General tab in Tools, Editor Options. Setting this value to 1 (one) offers the greatest flexibility.


Indenting both the braces and the code contained in them together is not as effective, because the braces are hard to pick out in the code. For single blocks of code this is not such a problem, but for nested blocks it can become confusing. That said, some still prefer this approach. For an alternative discussion of this, please refer to A Practical Handbook of Software Construction by McConnell, 1993. Be aware that most of the discussion presented in the book refers to languages that use keywords to show control block structures, such as begin and end, and the considerations involved are therefore somewhat different, though the distinction is not considered by the text. Tread cautiously.

When code is laid out as previously described—braces on their own line, matching pairs of braces lined up, and code within the braces indented—it can be read easily. Loop constructs can be marked as blocks, and nested constructs become very clear. No room is left for ambiguity. One instance in which this can be particularly useful is in the use of nested if-else statements. The following code is unclear:

#include <ostream>

// We have : int A <= int B <= int C

if(A + B > C)
 if((A==B) || (B==C))
 if((A==B) && (B==C)) std::cout << "This is an EQUILATERAL triangle";
else
if( (A*A + B*B) == C*C )
   std::cout << "This is a RIGHT-ANGLED ISOCELES triangle";
 else std::cout << "This is an ISOCELES triangle";
else
  if( (A*A + B*B) == C*C )std::cout << "This is a RIGHT-ANGLED triangle";
  else std::cout << "This is a TRIANGLE";
  else std::cout << "This is NOT triangle";

Its meaning is straightforward when written as follows:

#include <ostream>

// We have : int A <= int B <= int C

if(A + B > C)
{
  if((A==B) || (B==C))
  {
   if((A==B) && (B==C))
   {
     std::cout << "This is an EQUILATERAL triangle";
   }
   else if( (A*A + B*B) == C*C )
   {
     std::cout << "This is a RIGHT-ANGLED ISOCELES triangle";
   }
   else
   {
     std::cout << "This is an ISOCELES triangle";
   }
  }
  else if( (A*A + B*B) == C*C )
  {
   std::cout << "This is a RIGHT-ANGLED ISOCELES triangle";
  }
  else std::cout << "This is a TRIANGLE";
}
else std::cout << "This is NOT triangle";

Always consider if there is a better, clearer way to write code, especially if you find yourself writing large nested if and if-else statements. If you are writing a large if-else block, consider replacing it with a switch statement. This may not always be possible. In the case of large nested if statements, try restructuring the code into consecutive if-else statements.

In a similar vein, try to keep lines of code short. This makes it easier to read in the editor window and also makes it easier to print out.


Tip - Often when code is printed, some code lines are too long for the page onto which they are printed. When this occurs, one of two things happens: The lines are wrapped to the start of the next line or the lines are simply truncated. Both are unsatisfactory and degrade the readability of the code.

The best way to prevent this is to ensure that excessively long lines of code either are avoided or that they are carefully broken to multiple lines. When you are writing code in the Code Editor, the right margin can be used as a guide to the width of the printable page that you use. Change the right margin setting on the Display tab in the Tools, Editor Options menu so that the value represents the absolute printable right margin. In other words, set it so that characters that appear after this margin either are not printed or are wrapped to the next line (if Wrap Lines is checked in the File, Print menu). This will depend on the page size used and the value of the left margin setting (also in the File, Print menu).

For A4 paper and a left margin setting of 0, the right margin should be set to 94. This means that code lines that extend past this line will not be printed as they appear on the screen. To ensure that the right margin is visible in the Code Editor, make sure that the Visible Right Margin setting on the Display tab in the Tools, Editor Options menu is also checked.


Common reasons why code lines can become excessively long include heavily nested loops or selection constructs, use of switch statements with complex code inside, trying to write for and if statements on the same line, long function parameter lists, long Boolean expressions inside if statements, and string concatenation using the + operator.

For heavily nested loops or selection constructs, decrease your indent size if it is large or redesign the code so that some of the work is carried out by functions.

switch statements can be written differently. For example

switch(Key)
{
 case 'a' : // a very long line of code that disappears ...
       break;
 case 'b' : // another very long piece of code ...
       break;
 default : // Value not required – default processing
       break;
}

This can be rewritten as

switch(Key)
{
 case 'a' 

 : // a very long line of code that can now all be seen
  break;
 
 case 'b' 

 : // another very long piece of code that can also be seen
  break;

 default

 : // Value not required – default processing
  break;
}

With for and if statements all on one line, place the code executable part of the statement on a separate line. For example

for(int i=0; i<10; ++i) // long code that disappears ...

This can be replaced by the following:

for(int i=0; i<10; ++i)
{
  // long piece of code that no longer disappears 
}

The if statement can be similarly written. For example

if( Key == 'a' || Key == 'A' ) // long code that disappears ...

This can be replaced with

if( Key == 'a' || Key == 'A' )
{
 // long piece of code that no longer disappears 
}

In fact, it is better practice to write such one-line statements in this way, because it allows the debugger to trace into the line of code that is to be executed.

Long Boolean expressions inside if statements should be written on several lines, such as in the following:

if(Key == VK_UP || Key == VK_DOWN || Key == VK_LEFT...
{
 // Code here
}

This is better written as follows:

if(Key == VK_UP 
  || Key == VK_DOWN 
  || Key == VK_LEFT
  || Key == VK_RIGHT
  || Key == VK_HOME 
  || Key == VK_END)
{
 // Code here
}

This code raises an important point concerning the placement of the || or similar operator. The reason for placing it on the left side of each line is that we read from left to right. Placing it on the right makes the reading both slower and less natural, and it distorts the emphasis of the expression. Placing the operator on the right side of each line in the expression to show that there is more after the line is unnecessary, because people tend to read code by scanning blocks, not by reading individual lines as a computer does.

Long function parameter lists can be dealt with similarly, by placing each parameter on a new line. For example

void DrawBoxWithMessage(const AnsiString &Message, int...

This can be rewritten as shown in Listing 3.2.

Listing 3.2 Writing Long Function Parameter Lists

void DrawBoxWithMessage(const AnsiString &Message, 
            int Top,
            int Left,
            int Height,

            int Width);

It is important to place the comma at the end of each line. Placing the comma at the start of each line makes the code more difficult to read, because you would not expect to encounter a comma in this position in normal written text. The comma is for the compiler to separate the parameters and has no other meaning; its use should not unduly confuse someone reading the program. Note that the positioning of the comma is in contrast to the previous discussion of operator placement.

The same approach can be taken with long string concatenations:

AnsiString FilePath = "";
AnsiString FileName = "TestFile";

FilePath = "C:\\RootDirectory" + "\\" + "Branch\\Leaf" + ...

This can be rewritten as shown in Listing 3.3.

Listing 3.3 Writing Long String Concatenations

AnsiString FilePath = "";
AnsiString FileName = "TestFile";

FilePath = "C:\\RootDirectory" 
      + "\\" 
      + "Branch\\Leaf" 
      + FileName

      + ".txt";

The code in Listing 3.2 and Listing 3.3 is very clear, but it may not be very easy to maintain due to the amount of indentation required. For those who find adding lots of spaces difficult (some people really do!), an alternative approach is to use the standard indent for each of the following lines in such an expression. For example, if you are using a three-space indent, then the code from Listing 3.2 would become

void DrawBoxWithMessage(const AnsiString &Message, 
  int Top,
  int Left,
  int Height,
  int Width);

This degrades readability but results in code that is easier to maintain, a rather dubious trade-off, but sometimes a reasonable one.


Tip - You can save time writing code by using the code templates. These are accessed from the editor by pressing Ctrl+J and then using the up and down arrow keys or the mouse to select a template. However, remember that in order to maintain consistency with your own coding style and to get the best use of code templates, you should customize the templates that C++Builder provides. To edit code templates, go to Tools, Editor Options and select the Code Insight tab. Note that the | character is used to indicate where the cursor will be placed initially when the template is pasted into the editor. Code templates can also be edited manually in the $(BCB)\Bin\Bcb.dci file (where $(BCB) is the C++Builder 5 installation directory).


The Use of Sensible Naming

One of the best ways to improve the readability of code is to use sensible naming for variables, types, and functions. Type names include those used for naming actual types; for example int is the type name for an integer, TFont is the type name for the VCL's font class. Variable names are the names of variables that are declared to be of a specific type, for example, in the code

int NumberOfPages;

NumberOfPages is the name of a variable of type int.

Function names are given to functions to describe what they do. We'll consider variable names first, though most of what is said about variable names applies equally to type names and function names.

Choosing Variable Names to Indicate Purpose

Generally, you want to choose a name that reflects a variable's nature and purpose. If possible, it should also suggest what type the variable is. For example, String EmployeeName; is better than String S;.

When the variable pops up later in code, you will have no idea what S is for. For example, is it the number of pages in a book or a string representing a person's name? You also won't know what it is—for example, an int, a double, a String? EmployeeName is obvious: It is a variable that holds the name of an employee, and it is most likely a string.

Using descriptive names such as this is easy to do and makes everyone's life much easier when the code is read at a later date. Every time a new variable is declared, ask the question, "What is the variable's purpose?" Summarize the answer to the question and you have a sensible variable name.

When naming a variable, a word or short phrase is often used. Using a capital letter to start each word in the variable name is a popular method of making the name clear and easy to read. Others prefer all lowercase with underscores to separate words and, still others, a mixture of both. To illustrate, the previous declaration could be written as any of the following:

String EmployeeName;
String employee_name;
String Employee_Name;
String employeeName;

A disadvantage of using underscores is that variable names can quickly become very long. These are okay technically, but they start to make code look cluttered and also increase the symbolic appearance of variable names. Some suggest that all variable names should start with a lowercase letter, such as the employeeName example. This is often done to separate variable names from type names, which would typically start with a capital letter. Others start a variable name with a lowercase letter only when it is a temporary variable, such as a temp variable in a swap function. A compromise should be found so that a variable name is meaningful but also concise. That said, the meaningfulness of a variable name is most important.

Modifying Variable Names to Indicate Type

In general, knowing the purpose of a variable is often more important to the understanding of a piece of code than knowing what type the variable is. However, there are times when you may want to remind yourself of the variable type because special rules might apply. A simple example to illustrate this is given in Listing 3.4.

Listing 3.4 Illustrating the Need to Be Aware of a Variable's Type

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

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


double Average = Sum/20; // Sum is an int, needs cast as a double

The answer to the code in Listing 3.4 should be 123.5, but the value in Average will be 123. This is because Sum is an int, and when you divide by 20 (treated as an int), you get an int result that is assigned to Average.


Tip - Try to declare variables just before they are used; this helps ensure that variables are created only if they are actually needed. If code contains conditional statements (or throws an exception), it may be that some of the variables declared are not used. It is sensible not to incur the cost of creating and destroying such variables unless they are used. Be wary, though, of placing declarations inside loops, unless that is what is intended. It is also a good idea to initialize variables when they are declared. On a similar note, never mix pointer and non-pointer declarations on the same line. Doing so is confusing at best.

It is preferable to have variables declared as

Type VariableName;.

Hence, a pointer to an int should be written as

int* pointerToInt;

But if we write

int* pointerToInt, isThisAPointerToInt;

then PointerToInt is a pointer to an int and isThisAPointerToInt is not a pointer to an int, it is an int. Actually, this is what the declaration is saying:

int *pointerToInt, notPointerToInt;
This is still not clear, and the declaration is no longer written in the Type VariableName format. xThe solution is to write the declarations on separate lines. The ambiguity then disappears.x
int* pointerToInt = 0;
int notPointerToInt;

You should ensure that pointers are explicitly initialized either to NULL (0) or to some valid memory location. This prevents the accidental use of wild pointers (those that point to an undefined memory location).


To obtain the expected result of 123.5 for Average in Listing 3.4, the last line of the code snippet should be as follows:

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

This results in Sum being cast as a double before being divided by 20 (treated as a double). Average is now assigned the double value 123.5.


Tip - Make sure you are familiar with the four types of C++ casts, and always use C++-style casting in preference to C-style casting. C++-style casts are more visible in code and give an indication of the nature of the cast taking place.


This example is somewhat trivial, and adding type information to the variable name would be a bit like using a sledgehammer to crack a nut. A more sensible solution is probably to declare Sum as a double.

Sometimes type information may be required. One method of adding type information to a variable name is to use a letter (or letters) as a symbol at the start or at the end of each variable name. For example, you might use b for bool, s for a string, and so on. A problem with this approach is that type information can be added unnecessarily to too many different types of variables. The emphasis is then invariably placed on the type information and not on the variable's purpose. Also, there are many more possible types for a variable than there are letters of the alphabet. As a result, such symbols can themselves become confusing and complex. An infamous example of such a convention is the Hungarian Notation commonly seen in Win32 API code.


Note - The Hungarian Notation has its roots in Win API programming and advocates adding symbols to all variables to indicate their type. Since there aren't enough letters for all types, oddities in the notation are common. For example, Boolean variables are prefixed with an f, strings with an sz, pointers with p, and so on. A complete list is not appropriate here. The notation is infamous because many feel it creates more problems than it solves. These arise mostly from inconsistencies (such as the variable wParam being a 32-bit unsigned integer, not a 16-bit unsigned integer as the prefix implies) and names that are difficult to read. A perusal of the Win32 API help files should reveal some examples of the notation. Heavily typed notations are dangerous because they place the emphasis of a variable name on the variable's type, which often does not tell you much about the variable.


When reading such code, it is not always easy for the reader to mentally strip away the type codes, and this can decrease the code's overall readability. If you want prefixed (or even appended) type symbols, a compromise is to restrict the use of added symbols to only a few specific types.

Modifying Variable Names to Indicate Characteristics or Restrictions

A variable's name can also be used to convey information regarding some characteristic that a variable may have or to point out some restriction that may be applicable. It is important to know when a variable is a pointer, because pointers can easily wreak havoc in a program. For example, if you have a pointer to an int and accidentally add 5 to it without dereferencing it first, you have a problem. It is good practice to use a pointer only when no other type can be used, such as a reference (which are implicitly dereferenced).

It is also sometimes important to know when a variable is static, when a variable is a class data member, or when a variable is a function parameter. Suitable symbols that could be prefixed would be p_ for a pointer, s_ for a static variable, d_ or m_ for a class data member, and a_ for a function parameter. (An a_ symbol is used to differentiate a function parameter from a pointer and should be read as "where the parameter a_x represents the argument passed to the function." The use of a_ then becomes reasonable.)

An underscore is often used to separate a prefix from the variable name proper. This makes it easier to strip away the prefix when reading the code. If a separating underscore is used with an information symbol, then it is possible to append the symbol to the end of the variable name. This has the advantage of allowing the variable name to be read more naturally. For example, when reading s_NumberOfObjects, you would probably say "this is a static variable that holds the number of objects," whereas reading NumberOfObjects_s, you might say "this variable holds the number of objects and is static." This places the emphasis on the purpose of the variable (to store the number of objects). Which you prefer is probably a matter of personal choice. A problem with all such symbols occurs when more than one is applicable; then the syntax is not so tidy. Solutions such as always prefixing p_ for pointers and appending the other symbols can solve most of these problems, but not all.

Another common situation that often receives special attention is the naming of variables whose values do not change, in other words constants. Such variables are often named using all capital letters.

When you declare variables of certain classes, it is often sensible to include the class name (without any prefixed letter, for example T or C, if it is present; this is explained shortly) as part of the variable name. This is often done by prefixing some additional information to the name, or it can be as crude as appending a number to the class name as the C++Builder IDE does, though generally a little more consideration should be applied. For example, consider the following variable declarations.

TComboBox* CountryComboBox;
TLabel* NameLabel;
String BookTitle;

In the case of TComboBox and TLabel, it is appropriate to include the class name as part of the variable name. However, in the case of BookTitle, it is fairly obvious that it is a string, and adding the word String to the variable name perhaps does more harm than good.

Of special note in C++Builder is the naming of private member variables, which have a corresponding __property declaration. By convention, such variables are prefixed with the letter F (for Field). You should follow this convention and avoid using a capital F prefix for other purposes. The purpose of a prefix in this case is to allow the property name to be used unchanged (it is not necessary to think of a similar-sounding variant name).

Choosing Type Names

As was mentioned earlier, the naming of types should be approached in a fashion similar to the naming of variables. However, some conventions need to be considered.

For classes, convention says that if the class derives from TObject, then the class name should be prefixed with the letter T. This lets the user of the class know that it is a VCL-style class, and it is consistent with the naming of other VCL classes. This has a beneficial side effect. Variables declared of the class can use the class name without the prefix, making it obvious what the variable is. Non-VCL (that is, normal C++) classes can also use a prefix, but it is wise perhaps to use a prefix other than T, such as C to indicate that the normal C++ object model applies and reinforce that the class does not descend from TObject. This distinction can be important: VCL-style classes must be created dynamically; non-VCL classes do not have this restriction.

The naming of other types, such as enums, Sets, structs, and unions, can be handled in a similar fashion. By convention, C++Builder prefixes a T to enumeration and Set names, though some may prefer not to follow this convention, which has no specific meaning. Avoid using an E as a prefix; C++Builder uses this for its exception classes.

Because enums and Sets are commonly used in C++Builder, it is worth mentioning some points specifically related to their naming. A Set is a template class declared in $(BCB)\Include\VCL\sysset.h as

template<class T, unsigned char minEl, unsigned char maxEl> class RTL_DELPHIRETURN Set;

Ignoring RTL_DELPHIRETURN, which is present for VCL compatibility, we can see that a Set template takes three parameters: a type parameter and two range-bounding parameters. Hence, a Set could be declared as

Set<char, 'A', 'Z'> CapitalLetterMask;

If a Set is to be used more than once, a typedef is normally used to simplify its representation, as seen here:

typedef Set<char, 'A', 'Z'> TCapitalLetterMask;
// Later in the code
TCapitalLetterMask CapitalLetterMask;

Sets are often used to implement masks (as in this case), hence the use of the word Mask in the names used in the previous code. An enumeration is typically used to implement the contents of a Set. For example, the following definitions can be found in $(BCB)\Include\VCL\graphics.hpp:

enum TFontStyle { fsBold, fsItalic, fsUnderline, fsStrikeOut };

typedef Set<TFontStyle, fsBold, fsStrikeOut> TFontStyles;

Most enums used by the VCL are to facilitate the use of Sets. For convenience they are declared at file scope. Simply including the file allows easy access to the Set. This means that the potential for a name collision is high. To avoid such collisions, the "initials" of the enum name are prefixed to each of the values that the enum can take. This minimizes the chance of a name collision. This is good practice and should be used in your own code. For example, the initials of TFontStyle (excluding the T) are fs, which is prefixed to each of the enums. Another method that can be used to prevent name collisions is to place enums and typedefs inside the class definitions that use them. This means that such enums and typedefs, when called from outside the class, must be qualified by the class namespace. The same can be applied to const values.

Using a typedef in certain situations (such as this one) can improve readability. A typedef can also improve readability in the declaration of function pointers (and particularly __closures, i.e. events, discussed in Chapter 9, "Creating Custom Components"). Beyond situations such as these, typedefs should be used sparingly because you are actually hiding the type of the variable. Using typedefs too much will result in confusion.

Choosing Function Names

Function names should be precise and describe what the function does. If it is not possible to describe precisely what a function does, then perhaps the function itself should be changed. Different kinds of functions should be named in slightly different ways.

A function that does not return a value (a void function) or returns only function success information—in other words, those that return no data—should generally be named using an object verb name, such as CreateForm(), DisplayBitmap(), and OpenCommPort(). A member function of this type often does not require a qualifying object, because the object that calls the function generally can fulfill that role.

If a function does return a data value, then the function should be named to reflect the nature of the return value, such as GetAverage(), log10(), and ReadIntervalTimeOut().

Some prefer to make the first word in a function name lowercase. This is a matter of personal preference, but consistency should be maintained.

In general, function names can be longer than variable names, and if you need to write a long function name, you should not be overly concerned. However, take care to ensure that a long function name is not the product of a poorly designed function that tries to perform too many poorly related operations. In fact, you should endeavor to write functions that perform a single well-defined task, and the name of the function should reflect that. If you must write a function that does more than one thing, the name should make that obvious.

Adhering to Naming Conventions

How variables, types, and functions are named is an extensive topic, encompassing a myriad of concerns. Mentioning every convention that could be used is impossible. That being the case, you should endeavor to adhere to the following guidelines:

  • Name a variable or a function such that its purpose is obvious. If this means more typing, then so be it.

  • Name a type so that its intended use is obvious. Try not to use a name that is an obvious choice for a variable of the type. Prefixing a letter symbol can help.

  • Be consistent! With practice, even the most obscure naming system can be understood if it is consistently applied.

The Use of Code Constructs

One of the best ways to improve the readability of code is through the appropriate use of code constructs—using the right tool at the right time for the right job. Therefore it is important to understand when to use const (and when not to), when to use references instead of pointers, which loop statement is most appropriate, whether to use multiple if-else statements or a single switch statement, when to represent something with a class and when not to, when to throw an exception and when to catch it, how to write an exception specification, and so on. Most of these are design issues, but some are simple to add to code and can improve not only readability but also robustness. The use of const and references (along with other coding issues) is discussed in the next section. This is an area of programming in which you can continually improve.

The bottom line is this: If you know what you are doing when you write code, and you are doing it for the right reasons and in the right way, then your code will be easier to follow. This is because what the reader expects to happen will happen. If it doesn't, the reader will become confused by the code, which is to be avoided.

The Use of Comments

The main purpose of comments is to allow the annotation of the code to improve readability. Judicious use of comments does just that, but care needs to be taken not to make the code untidy by putting comments here and there without any real strategy.

Comments can be applied throughout implementation code to annotate specific areas where confusion might arise. If you do this, use only C++-style comments beginning with //. This prevents comments from being finished prematurely by the unexpected occurrence of a C-style comment end, */.

Using Comments to Document Code

How comments are added to implementation code is important. If it is done poorly, the effect can be to make code even more unclear. If a few guidelines are followed, comments can improve the readability of code.

It is important not to interrupt the layout of the code. Comments should be separated from the code, either with space or a differing style (such as italic) or by using a different color. A comment should be indented when code is indented. This is particularly important if the comments discuss the code's functionality. Comments written in this way can be scanned independently from the code, and the code can be scanned independently from the comments. Some programmers prefer not to separate a comment line from a code line, because they feel that use of color and style is sufficient to separate the comments from the code.


Tip - By modifying the settings on the Colors tab in the Tools, Editor Options menu, it is possible to remove or highlight comments as required. To hide a comment from view in the editor window, simply change the color of the comment so that it matches the editor window background color; in the Defaults setting this is white. To highlight a comment, try changing the background color to blue and setting the foreground color to white. You can experiment to see what you like most.


If a comment is specifically related to a single line of code and room permits, it is better to add the comment on the same line following the code. If several lines require such comments, an effort should be made to ensure that each comment starts at the same column in the editor. The advantage of such comments is that they do not interfere with the code's layout. For example, code could be commented as follows:

double GetMaximumValue(const std::vector<double>& Vector)   throw(std::out_of_range)
{
  // Initialize Maximum, if the vector is empty an 
  // std::out_of_range exception will be thrown

  double Maximum = Vector.at(0);   
  
  for(int i=0; i<Vector.size(); ++i)
  {
   if(Vector[i] > Maximum)  // If the ith element is greater 
   {             // than the current value in
     Maximum = Vector[i];  // Maximum then set Maximum equal 
   }             // to its value 
  }
  
  return Maximum;
}

The comments for the if statement are superfluous, but they have been added to illustrate how comments can be laid out. Comments can also be appended to the closing brace of loop and selection statements:

}//end if
}//end for
}// ! for
}//end for(int i=0; i<Vector.size(); ++i)

The last example might be too much sometimes, but it can be useful when writing code or when a loop or selection construct spans more than one page.

Comments are very useful when documenting code. They are particularly useful for summarizing important information about a function's operational characteristics. There are many ways to present such information, but the important thing is to use one method consistently. If there is something important to say about a function, the information should appear in the header file that declares it and in the implementation file that implements it. Whoever maintains the code needs as much information as the user of the code. You should always have a brief summary of a function's purpose above the function implementation and at the function's declaration. You should also list any requirements that must be met for the function to operate as expected (sometimes referred to as preconditions) and any promises that the function makes to the user (sometimes referred to as postconditions). Any condition that results in undefined behavior should be explicitly stated and included in any list of requirements. Information presented in this way can be thought of as a contract for the function—if you do this, then the function promises to do that.

Only enough information as is necessary needs to be documented. For short, simple functions, little needs to be written. Conversely, large complex functions may require more lengthy comments. If this is so, two considerations must be kept in mind. Users of a function don't need to know of implementation issues internal to the function or how a function affects any private or protected data (assuming the function is a class member function). Such information should not appear in a header file. Also remember that comments are most useful when they are close to where they are directed in the implementation. It may be that some of the description is too detailed and some of the information given would be better placed elsewhere. An example of commenting a function in the implementation file would be as follows:

//-----------------------------------------------------------//
//
// PURPOSE : Returns the maximum value present in a vector 
//      of doubles
//
// REQUIRES : The vector passed is not empty
//
// PROMISE : The function will return the value of the 
//      largest element
//
//-----------------------------------------------------------//
  
double GetMaximumValue(const std::vector<double>& Vector)   throw(std::out_of_range)
{
  double Maximum = Vector.at(0); 
  
  for(int i=0; i<Vector.size(); ++i)
  {
   if(Vector[i] > Maximum) 
   { 
     Maximum = Vector[i];
   } 
  }
  
  return Maximum;
}

Similarly, the header file could look like this:

double GetMaximumValue(const std::vector<double>& Vector)   throw(std::out_of_range)
  // PURPOSE : Returns the maximum value in a vector of doubles
  // REQUIRES : The vector passed is not empty
  // PROMISE : Returns the value of the largest element

Remember that the header files in a program are often the only up-to-date documentation available for an interface, and as such they should be clear and accurately maintained (kept up-to-date). If this is not done, comments can quickly become useless. If code is changed, then always change any comments that relate to that code.

If you find yourself having to write extensive comments to explain a particularly tricky piece of code, it may be that the code itself should be changed.

Using Comments to Ignore Code

Another use of comments of particular note to C++Builder is the commenting out of the names of unused parameters in IDE-generated event handlers. It is common that some or all of the parameters are not used by the function. Commenting out the parameter names does not alter the function signature, but it does show explicitly which of the parameters are actually used. If parameters are written on the same line, then C-style comments must be used to achieve this. The parameter list can be rearranged to allow commenting with C++-style comments, but the result looks untidy. Therefore, caution must be exercised. The following code snippet of an event handler for a TButton MouseUp event is shown for illustration:

void __fastcall TMainForm::Button1MouseUp(TObject* /*Sender*/,
                     TMouseButton /*Button*/,
                     TShiftState /*Shift*/,
                     int X,
                     int Y)
{
  // Display the cursor position within Button1
  Label1->Caption.sprintf("%d,%d", X, Y);
}

Tip - The MouseUp event handler uses the sprintf() AnsiString member function to modify the Caption property of Label1. This works because the sprintf() member function returns the AnsiString (*this) by reference. In general, however, a property should be modified only by assigning a new value to it using the assignment operator.


It is possible to delete the unused parameter names, but this can make the function header confusing, especially if you decide to use one of the parameters at a later date. A solution is to comment out the parameter name and then replace the commented out comma or closing bracket before the comment, as follows:

void __fastcall TMainForm::Button1MouseUp(TObject* ,//Sender,
                     TMouseButton ,//Button,
                     TShiftState ,//Shift,
                     int X,
                     int Y)
{
  // Display the cursor position within Button1
  Label1->Caption.sprintf("%d,%d", X, Y);
}

This is effective and easily maintainable. If you want to use the parameter later, simply delete the // and the extra comma or extra closing bracket. An alternative is as follows:

void __fastcall TMainForm::Button1MouseUp(TObject* ,//Sender
                     TMouseButton ,//Button
                     TShiftState ,//Shift
                     int X,
                     int Y)
{
  // Display the cursor position within Button1
  Label1->Caption.sprintf("%d,%d", X, Y);
}

This differs only in the removal of the redundant commas from the end of each comment. You may find this is an improvement, because a comma (or bracket) at the end of a comment could be a distraction.

Using Comments to Improve Appearance

A final use of comments is to help improve the overall appearance of the code as it appears on the screen or when it is printed. This is done by placing boxes around headings, placing divider lines between functions, and so on. When using comments in this way, care should be taken not to obscure the code within a forest of * characters or other such symbols. You must also be consistent for this to be useful.

C++Builder automatically places a divider line between blocks of code that it generates. This helps improve the appearance of the code, because there is additional visual separation. It is good practice to do this with your own code.


Tip - C++Builder 5 lets you change the default divider line that it places between sections of code that it generates. You can do so by editing the following entry in the BCB.BCF file in the $(BCB)\BIN folder. If the entry does not exist, you will have to create it. As you can see, the format is similar to an *.ini file.

[Code Formatting]
Divider Line=//---- My Custom Divider Line ----//

The divider line should begin as a comment line, with //. A good method of adding the line you want is to first write it in the Code Editor, then cut and paste it to the BCB.BCF file. That way you know exactly what it will look like. This helps IDE-generated code look consistent with your own code. It is a good idea to add this divider line to your code templates.


A Final Note on Improving the Readability of Code

Ultimately, the best descriptor of the code's purpose and how it operates is the code itself. By improving the code's readability, you enhance this description.

  • + Share This
  • 🔖 Save To Your Account

Related Resources

There are currently no related titles. Please check back later.