Coolquest, Inc. Home Products Support About Contact
cbold_logo_gif C++BOLD Reference Manual cbold_logo_gif

<<< Previous CBOLD Reference Home Next >>>

 

21. C++ Primer

Under Construction

CBOLD provides a design notation that is concise and intuitive, but the designer not familiar with C++ may well be left wondering "How does it all work?" For example, the notation for connecting two ports together and assigning the connection the name of "INTREQ" is:

"INTREQ"  <<  HO.INTREQ  <<  VMEC_PLD.INTREQ;

Normally, the << operator shifts its left operand by the number of bits specified by its right operand. How does it make any sense to shift a string constant ("INTREQ") by a TPort (HO.INTREQ ) and then shift the result by another TPort (VMEC_PLD.INTREQ)? How can CBOLD use the same operator (<<) to output a message? For example:

Log << "Connecting";

An introduction to some of the features of C++ will help answer questions like these. This chapter does not cover all C++ language features-just those CBOLD uses heavily. The references, e.g., Stroustrup, may be consulted for full details.

Section Contents

21.1. Classes and Inheritance
21.2. Constructors and Destructors
21.3. Static Variables and Members
21.4. Virtual Functions
21.5. dynamic_cast
21.6. Runtime Type Information (RTTI)
21.7. References
21.8. Function Overloading and Default Arguments
21.9. Operator Overloading
21.10. Type Conversion Operators
21.11. Exceptions
21.12. Macros
21.13. enum
21.14. The Standard Library

21.1. Classes and Inheritance

21.2. Constructors and Destructors

21.3. Static Variables and Members

21.4. Virtual Functions

Virtual functions allow a derived class to specify new behavior for a member function of a base class. A base class with such a member function is sometimes referred to as polymorphic.

The virtual functions a designer most often encounters when using CBOLD are TModPar::Register() and TModule::Connect(). For example, when coding a new module, the designer derives a new type from TModule and codes its Register() and Connect() functions as appropriate to the new type:

class CM_MyModule : public TModule {
public:
  ...                          // members specific to this module go here
  virtual void Register() {
  ...                          // member ports, modules, etc. are registered here
  }
 
  virtual void Connect() {
  ...                          // connections are made here
  }
};

Because Register() and Connect() are virtual, CBOLD does not need to know the exact type of a module in order to call the Register() and Connect() appropriate to the module-it needs only a pointer to the base object.

21.5. dynamic_cast

21.6. Runtime Type Information (RTTI)

21.7. References

According to Stroustrup, "A reference is an alternative name for an object." For example:

int  Foo = 0;
int& Bar = Foo;  // Bar is an alternative name for Foo
Bar++;           // Bar's value is now 1, as is Foo's
int& Bop;        // illegal--references must be initialized, otherwise they would refer to nothing

In some ways, a reference is like a pointer, but requires the same syntax as a non-pointer. Unlike a pointer, a reference must be initialized and always refers to the same object, i.e., it cannot be changed to refer to some other object.

When a function argument is a reference, the argument is passed by reference, not by value, and the function can change the object to which the reference refers:

void SetDefaults( TCar& Car ) {
  Car.Doors = 4;
  Car.Color = "red";
  ...
}
TCar MyCar;
SetDefaults( MyCar );   // set MyCar.Doors to 4 and MyCar.Color to "red"

The above example could be rewritten to use pointers:

void SetDefaults( TCar* Car ) {
  Car->Doors = 4;
  Car->Color = "red";
}
TCar MyCar;
SetDefaults( &MyCar );   // set MyCar.Doors to 4 and MyCar.Color to "red"

Several of the CBOLD connecting operators take reference arguments, allowing them to operate efficiently without requiring the user to pass pointers.

21.8. Function Overloading and Default Arguments

21.9. Operator Overloading

In C++, operators can be given new meanings, usually to provide concise notation for operations on user- defined types. The new meanings may have nothing to do with the traditional meaning of the operator. For example, the standard library overloads the shift operators (<< and >>) to allow concise expression of stream input and output:
for ( int LoopCount = 1; LoopCount <= 10; ++ LoopCount ) {
  ...
  cout << "\nEnd of loop " << LoopCount << ".";  // e.g., print out "End of loop 5."
}

For example, the arithmetic operators (+, –, etc.) might be overloaded to allow users of class TComplex to perform arithmetic operations using the same notation they use for real numbers.

TComplex Z1 = 0, Z2 = 0, Z3 = 0;    // three complex numbers
...
Z1 = Z2 + Z3;    // complex addition 

21.10. Type Conversion Operators

21.11. Exceptions

Exceptions allow a C++ program to abandon the current path of execution and begin an alternate path when the program detects something exceptional. CBOLD uses the C++ exception mechanism in a traditional way: to handle errors. To make matters even simpler, CBOLD errors are always fatal, i.e., after an error message is printed, a CBOLD application terminates. Because most errors are reported via CBOLD's BEGERR and ENDERR macros, the CBOLD source code shows little evidence that exceptions are being used at all.

An exception is said to be "thrown" from the point of the error and "caught" by the exception handler. The throw keyword can be seen in the definition of the ENDERR macro:

#define  ENDERR   '.'; throw Temp_OStringStream_.str(); }

In addition to changing the path of execution, the throw may transfer information to the new path of execution in the form of the object thrown. In the case of ENDERR, the object thrown is a string that contains an error message.

The catch keyword can be seen near the end of main():

int main(int argc, char* argv[])
{
 
try {
  ...                                                                       // normal execution
  Log << "Registering";                                                     //       |
  reg( Root );                                                              //       |
                                                                            //       |
  Log << "Connecting";                                                      //       |
  Root.ConnectAll();                                                        //       |
                                                                            //       |
  Log << "Post-Connecting";                                                 //       |
  Root.PostConnectAll();                                                    //       |
  ...                                                                       //       |
                                                                            //       |
  cout << "\nPress Enter to continue.\n" << flush;                          //       |
  cin.get();                                                                //       V
}
catch ( string& aString ) {                                                 // exceptional execution
  cout << "\n\nError:  " << aString << '\n';                                //       |
                                                                            //       |
  MessageIfError( cout, TModPar::RegisteringModPar,    "registering"     ); //       |
  MessageIfError( cout, TModPar::PostConnectingModPar, "post-connecting" ); //       |
  MessageIfError( cout, TModule::ConnectingModule,     "connecting"      ); //       |
  cin.get();                                                                //       |
  return 1;   // error                                                      //       V
}
 
return 0;     // no error                                                   // exit main()
}

The try block identifies code in which an exception might be thrown. The catch block specifies what to do if an exception is thrown. The catch block is entered only if the type of the object thrown is compatible with the type specified in the catch, in this case a string.

An exception may be thrown at any call depth. In the case shown above, no code within main() itself throws an exception, but an exception might be thrown by Root.ConnectAll(), by some function called by Root.ConnectAll(), or by some function called by that function. When an exception is thrown, execution of the program so far is to some extent "undone" as the stack is "unwound." This proceeds until a catch capable of handling the thrown exception is encountered. In the case of CBOLD, there is typically only one try and one catch, and the catch is designed to catch any exception that is thrown by CBOLD (e.g., via BEGERR...ENDERR).

21.12. Macros

In general, the use of macros in C++ is not recommended. CBOLD relies on macros in rare cases, primarily to enhance notational convenience. For example, the Registering Macros provide concise and readable notation that would be difficult to implement without macros. The best reason for the user to avoid defining new macros is their possible interference with commercial IDE's and CBOLD Support Tools. These tools typically do not attempt to expand macros or interpret their definitions.

21.13. enum

CBOLD designs use enumerators to define named integer constants. For example:

class CM_MyModule : public TModule {
public:
  enum { vcc_cdc_count = 24,              // define constants
         vcc_tdc_count =  3 };
  CP_CDC_POS  VCC_CDC[ vcc_cdc_count ];   // ceramic decoupling
  CP_TDC_POS  VCC_TDC[ vcc_tdc_count ];   // tantalum decoupling
  ...
  virtual void Register() {
    rega( VC_CDC, vc_cdc_count );         // auto_reg
    rega( VC_TDC, vc_tdc_count );         // auto_reg
    ...
  }
 
  virtual void Connect() {
    wireall( GND );
    for ( int i = 0; i < vcc_cdc_count; ++ i )  VCC << VCC_CDC[ i ].POS;
    for ( int i = 0; i < vcc_tdc_count; ++ i )  VCC << VCC_TDC[ i ].POS;
    ...
  }
};

To change the number of decoupling capacitors in this module, you would change the values of vcc_cdc_count and vcc_tdc_count. If the constants 24 and 3 were used throughout the example instead of the enum, you would need to make changes at several places within the source code and the code would be poorly documented. If #define had been used to define the constants, then the names vc_cdc_count and vc_tdc_count would be defined globally.

The enum declares the names vcc_cdc_count and vcc_tdc_count within the scope of CM_MyModule. Within another scope, these names can be re-used. If the names are re-used in an enum in a different scope, they can be defined with different values.

21.14. The Standard Library

The C++ Standard Library is a set of classes, functions, operators, etc., that is not part of the C++ language itself but is available for use with most compilers.

CBOLD relies heavily on the following from the standard library:

streams
containers, especially vectors
strings

 

<<< Previous CBOLD Reference Home Next >>>

Legal Copyright © 2007 by Coolquest, Inc. Contact