Coolquest, Inc. | Home | Products | Support | About | Contact | |||
|
<<< Previous | CBOLD Reference Home | Next >>> |
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.
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 |
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.
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.
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
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).
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.
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.
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 |