Method and apparatus for interactively generating a computer program for machine vision analysis of an object5481712Abstract A system for interactively generating a computer program for machine vision analysis insures that the program is correct by permitting the operator to make only syntactically correct modifications to the program. The system includes an element for storing the computer program being generated. A further element displays the program to the operator. A positioning element demarks a location of interest within the program. A menu element displays permissible programming modifications for the location of interest. The menu element incorporates in its display of permissible programming modifications statements for machine vision analysis of an object image, e.g., calls to machine vision subroutines and functions. To facilitate specification of input parameters to those subroutines and functions, the imaging element can generate a candidate image of the object upon which the machine vision analysis is to be run. A graphical input element displays over that candidate image a graphical icon that the operator can manipulate to specify the parameters. A textual input element can display an icon, e.g., a dialog box, prompting the operator to designate textually input parameters for the machine vision tool. An update element responds to the operator selection by appropriately modifying the stored program. Claims In view of the foregoing, what is claimed is: Description BACKGROUND
______________________________________
// compute a cumulative function
struct e { int x, cum;};
ccArray<e> a(100), p;
(*a).cum = (*a).x;
//a-> is the same as (*a)., as usual
for (p = a + 1; p.indexOK( ); ++p)
// note pointer arithmetic
p->cum = p->x + p[-1].cum;
______________________________________
The new features are: 1) An array pointer can be uninitialized, can point to an element of an array, or can point one beyond the last element of an array. 2) Any pointer arithmetic that results in a value before the beginning or more than one past the end of an array is trapped. Any attempt to reference an element before the beginning or past the end of an array is trapped. Note that negative indicies may be OK, as in the above example. Any attempt to use an uninitialized pointer is trapped. 3) Storage is managed automatically, so that when all pointers to an array have been destroyed, the array is destroyed. 4) Elements can be inserted and deleted. When an element is inserted, the index of elements after the insertion point increases by 1; when an element is deleted, the index of elements after the deletion point decreases by 1. Existing array pointers to elements in an array, by contrast, are unaffected by insertion and deletion (except see #5) they still point to the same element. 5) When an element is deleted, any existing pointers to that element are considered stale, and any attempt to use them is trapped. A stale pointer can be reassigned to a fresh value. 6) Arrays are polymorphic any of a variety of storage management styles can be chosen when the array is constructed to satisfy requirements such as zero storage overhead, insert/delete speed, random access speed, heap usage, and assembly language compatibility. These styles are totally interchangable and invisible to the non-assembly language array user. 7) Common array operations are very efficient. The operations *p, p.fwdarw., and checking to see if a pointer has reached the end of an array are nearly as fast as for native arrays, for all array storage management styles. 2. Design Overview Arrays are based on the handle/rep class style, but differ sufficiently from the standard handle classes That they are not derived from ccHandle<T> and ccHandleBase. The handle class ccArray and the rep classes derived from the abstract base cc.sub.-- Array are template classes of one class parameter, the data type that the array will hold. The rep class is a friend of the handle class, but not the other way around, so that implementation details are confined to the rep class and not spread to the handle. The handle class and the rep base class implement basic functionality that is common to all array types (storage management styles), and define a small number of pure virtual functions for derived classes to override to plug in a specific storage management style. An array pointer (i.e. handle) can be in four states: uninitialized, pointing to valid element, pointing 1 beyond end, and stale. It is a central property of the design that handles are compact and can be used efficiently but safely, without unnecessary storage overhead for representing the states and without excessive time spent testing the state. Careful consideration was given to the act of iterating over an array, where for each iteration one must: check for the end, reference the current element one or more times, and advance to the next element. It is important that each of these three operations is safe in isolation, yet when used together there is minimal redundent testing of state. In the specified design, all of the real work is done when advancing to the next element referencing and checking are simple inline functions, yet still safe. Finally, note that these properties must hold in spite of insertion and deletion operations, and for almost any conceivable storage management style. The handle class is derived from ccUnwind and contains a pointer to the rep class, a pointer to an element of the array, and a link pointer so that handles can be put on lists--handle is 20 bytes and the constructors are terse. The rep class pointer is always valid and is never tested--for uninitialized handles, the pointer is not null but points to a static instance of a rep class where all of the virtual functions are overridden to throw an error. The element pointer either points to a valid element of the array or is null--this makes the referencing operations *p and p.fwdarw., and checking for valid pointer, fast. A null element pointer could mean either pointing 1 beyond the end or stale--the state is implied by which list the handle is on, as described below. All existing handles for a given rep class are chained together on one of two lists maintained by the rep class, one for fresh handles and one for stale. The lists serve several purposes: 1) To distinguish stale handles from end-pointing ones, with zero space and testing time overhead. 2) To determine when the rep class can be destroyed (both lists are empty). 3) To find handles that must be made stale as the result of a delete operation. 4) To allow certain storage management schemes to find and update handles if insertion or deletion operations cause element addresses to change. Note that clients of ccArray can obtain the actual address of array elements using either &*p or &p[i]. This is dangerous, because none of the bounds checking, stale checking, relocation adjustment, and storage management operations apply to native pointers. We could prevent clients from obtaining raw addresses by specifying a different syntax and semantics, but the cost, complexity, and inconvenience seem excessive. Furthermore, it is clear that real-world engineering considerations will occasionally require us to use the raw addresses, presumably under carefully constrained circumstances that include knowledge of the storage management scheme in use. Thus we do not forbid clients to obtain a raw address, but we do discourage it. 3. Detailed Design ccArray ccArray<T> is a custom handle class. No derivation from ccHandle is needed since it's functionality is embedded in ccArray. ccArray contains a pointer to the rep class to use. This pointer is always valid. An uninitialized pointer is one that points to a class called cc.sub.-- UninitArray. All member functions throw an uninitialized error. In contrast, ccHandle performs a check everytime an access to the handle is made. ccArray has the element of a starting element which does NOT have to be the first element in the buffer. Negative indices are used to access elements which are before the starting element. If an element is removed from the list that another handle is pointing to, that class is removed from the handleList and placed on the staleList. The rep class keeps track of which handle classes are accessing data for a couple of reasons. The first is to check that valid accesses are being made. A null pointer indicates a problem with the handle. If the handle is on the stale list, the error occurred because the class pointed to an item that is no longer on the list. The second reason is that some implementations of the rep class may have to dynamically relocate data. For example, the gap array may move elements around to optimize the process of inserting new elements. When blocks are moved, the handleList is examined to see if any handles are pointing to an affected element. These pointers are corrected to point to their new location. The primary member functions are: ccArray (type, size) Constructs an array of the specified type and size. Current choices are: ccArray (ccArrayType::fixedArray, size) Creates a fixed array of indicated size. Array elements are not initialized. ccArray (ccArrayType::byteGapArray, size) Creates a gap array of the indicated size. Byte copying is used to move elements in the buffer. ccArray (ccArrayType::ctorGapArray, size) Creates a gap array of the indicated size. A constructor (using placement syntax) is used to move elements in the buffer. int index () Returns the index of the current element relative to the starting element in the buffer. The starting element is mearly a pointer to any element. Negative indices can result if the starting element is not the first element. int indexOK () Returns true if the current element is valid. int indexOK (int index) Returns true if the indicated index is valid. The index is relative to the starting element. void indexCheck () Checks the current element for validity and throws an error if it isn't. The error can either be stale or bounds error. void indexCheck (int index) Checks the specified index for validity. A bounds error is thrown if the index is not valid. ccArray<T> first () Returns a new handle whose current element is the first one on the list. ccArray<T> end () Returns a new handle whose current element is one past the last one on the list. This is used primarily to insert new items at the end of the list (insertions always are made in front of the current element). T& operator [] (int index) Returns a reference to indicated element. A bounds error is thrown if the index is invalid. T& operator * () Returns a reference to the current element and throws either a bounds or stale error if the current element is invalid. ccArray operator+(int index) ccArray operator-(int index) Returns a new handle whose current element is relative to the original one by the specified amount. A bounds error is thown if the new element is invalid. ccArray& operator+=(int index) ccArray& operator-=(int index) Moves the location of the current element by the relative index, bounds error is thown if the new element is invalid. ccArray& operator++() ccArray& operator--() ccArray operator++(int) ccArray operator--(int) Pre and Post increment/decrement the current element. Pre decrement/increment returns a new handle. A bounds error is thown if the new element is invalid; ccArray insert () Insert a new element in front of the current element. The new element remains uninitialized, An unimplemented error is thown if the array type does not allow insertions. ccArray insert (const T&) Insert the specified element in front of the current element. The new element remains uninitialized, An unimplemented error is thown if the array type does not allow insertions. ccArray& remove () Remove the current element from the array. A bounds or stale error is thown if the element is invalid. Otherwise it is removed from the list and all other handles that currently point the element are moved to the staleList. ccObjArray ccObjArray<T> is identical to ccArray (it's derived from it) except the addition of a single member function. The "->"operator is defined to allow direct access to elements of the objects in the array. It's pretty easy to decide when ccArray or ccObjArray should be used. Basic types such as int's and float's must use ccArray since the .fwdarw. syntax has no meaning (besides, the compiler will generate an error during the instantiation). Arrays of objects or structs can use either one but it's better if they use ccObjArray to avoid the need to someday change it. T* operator .fwdarw. () Returns a pointer to the current element so that access to one of its elements can be made. cc.sub.-- Array cc.sub.-- Array<T> is the rep base class. Only the handle class can access its member functions. cc.sub.-- Array is virtual and requires a derived class to define at least five functions. Four other functions are optional and are defined either to get functionality or efficiency. cc.sub.-- Array owns the linked lists for handleList and staleList (The linked lists are used because they're simple and efficient). Any handle class that references the array has an entry on one of these lists. An entry can only get on the staleList if the current element that it points to is no longer on the list. The virtual functions that must be defined by all derived classes are: T* lookup (T* base, int index) Returns a pointer to the element at the specified index relative to the specified base. A bounds error should be thrown if the requested element is not in the array. 0 is returned if the element points to one past the end of the list. This state is not an error condition. int index (T* base, T* rel) Computes the index of the item relative to the specified base element. T* insert (T* beforeHere) Inserts a new element before the specified one. A bounds error is thown if the specified element is invalid. The behavior when the list is full or no insertions are allowed is application specific. See the derived classes for specifics. T* insert (T* beforeHere, const T& value) Inserts the specified element before the specified one. The same requirements lists above still apply. T* remove (T* thisOne) Remove the specified element from the array. The handle cl ass insures that the element is on the list. Other virtual functions that can be overridden: T* access (T* base, int index) Exactly like lookup except that a bounds error is thrown if the element is one past the end of the array. access() is used instead of lookup() when data must be retrieved. int indexOK (T* base, int index) Returns 1 if the current index relative to the specified base is valid, otherwise a 0 is returned. It's implemented by setting up a catch handler and executing the lookup function. It's best to override this if possible. ccArray compress () Compress the current array. The default behavior is to return a new handle to the current array. Some derived classes like the gap array override this to return a new array with all the unused space removed. cc.sub.-- UninitArray Derived from cc.sub.-- Array. All the virtual functions in cc.sub.-- Array are defined to throw an uninitialized error. Creating a ccArray of no specific type creates a handle which points to a static instance of cc.sub.-- UninitArray. cc.sub.-- FixedArray cc.sub.-- FixedArray<T> implements a fixed array. Once constructed, elements cannot be inserted or removed or else an unimplemented error is thrown. Construction is of the form: ccArray (ccArrayType::fixedArray, size) where size if the size of the array. cc.sub.-- GapArray cc.sub.-- GapArray<T> implements a gap array. Elements can be inserted and removed but the emphasis is placed on insertion. The buffer space allocated to the array looks like: ##STR1## Construction is of the form: ccArray (ccArrayType::byteGapArray, size) Elements of a byteGapArray are copied internally using cu.sub.-- copy. A range of elements can be moved using a single call. ccArray (ccArrayType::ctorGapArray, size) Elements of a ctorGapArray are moved using a copy constructor with placement syntax. Movement is much slower because elements must be moved one at a time. The destructor is explicity called after the element has been moved. If you see this part of the code commented out it's because of a Cfront bug because the dtor call is inside a template. The byte and ctor variations are implemented as a single class because most operations are identical. I didn't see the benefit of implementing them as separate classes that derive from a common base class. Insertion: Insertions can be done at very high speed. The basic steps are: If the insertion point is not the .sub.-- point, move the records so that it is. The handles in the handleList are scanned to see if any of them point to an element that was relocated and corrects for it if it is. For multiple insertions at the same point, this overhead occurs only for the first insertion. The new element is copied into the buffer at .sub.-- mark. .sub.-- mark is incremented to point to the next available location. Remove: Removals of neighbors lower in the list can be done at high speed. The basic steps are: If the item to be removed is not at .sub.-- point, move elements in the buffer until it is. This step is identical to the first step of insert. Remove the element at .sub.-- point by incrementing .sub.-- point. A ctorcopy array will call the destructor first (assuming the Cfront 3.0 bug gets fixed). A list that is full is indicated by the .sub.-- mark and .sub.-- point being equal. The compress() function removes any unused space by creating a new ccArray of a size that will just hold the elements and then copies them from the old array to the new one. Exceptions Exception Handling in the Cognex Vision Class Library (VCL) 1. Client Usage The exception handling system in the VCL is the key to what we can consider to be "safe" programming. The VCL uses this library based scheme, at least until implementations of the ANSI proposed mechanism are available. This implementation uses a minimum of intrusion on the developer of VCL classes and provides stack unwinding, including frames from inside partially constructed objects. Stack unwinding is the correct destruction of automatic and temporary (on the stack) objects in reverse order of the scopes they have been destroyed in. For built in types (double, int, et. al.) and objects without a destructor, this is a null operation. For objects with a destructor, this means invoking it, just as if control flow had left its enclosing block. EXAMPLE
______________________________________
class x {
public:
X( );
.about.X( );
};
ccSignal Xalloc = "Exception Signal to be handled";
int bar ( ) { Xalloc.Throw ( ); }
int foo ( )
X blue;
bar ( );
X red;
}
int main ( )
{
cmCatch (Xalloc, xsignal);
if (xsignal)
cout << "Xalloc signal caught" << endl;
else
{
foo ( );
cout << "No signal caught" << endl;
}
cout << "Exit program" << end
}
______________________________________
In the example, the class X has a destructor. Automatic objects of type X will need to be destroyed correctly when the stack is "unwound" past them. An exception to be caught is declared. The type of an exception in the VCL is ccSignal. Exceptions are also called signals, but should not be confused with asynchronous signals (interrupts) from the ANSI C library. Only static objects of class ccSignal may be declared and the constructor takes a char* argument which represents a string to print in error conditions. A function that throws this signal, int bar (), is defined. The syntax of the throw in the VCL system is used. This is slightly different than the ANSI syntax. Note that "throw" is a C++ keyword even now, so we use a member function of ccSignal named "Throw". "int foo ()" is a function that creates some automatics and calls a function. Function foo has no idea that an exception may be thrown and doesn't know what to do with any signal thrown (it has no catch handlers). The main function declares a catch handler. This is a declaration of intent to stop the stack unwinding and deal with the error in some way. The first argument is the signal to catch, the second behaves like a declared pointer to ccSignal. If it is null, no exception has been caught. Otherwise, it points to the exception caught. The .sub.-- if.sub.-- statement represents the "try block" and the "catch clause". The syntax of catch clauses and try blocks in our system is different from the ANSI syntax, but the functionality is similar. A try block is a group of statements that might throw an exception that you would like to handle. A catch clause is what you will do once you catch such an expression. The example starts the program in main and sets up a catch handler. The catch handler implicitly declares a pointer to ccSignal named xsignal. The catch clause is the true branch of the if statement and the try block is the false branch. The try block calls foo (). If foo returns, it will print "No signal caught". The function foo creates an X named blue and calls bar. Bar throws Xalloc. At the point of the throw the stack looks like this:
______________________________________
int main ( )-
local: xsignal
Catch handler for Xalloc
int foo ( )-
local: blue
int bar ( )-
throw point of Xalloc
______________________________________
The X named red has not been constructed. Stack unwinding begins, and we go up out of bar into the frame of foo. The X named blue is destructed, but the X name red is not (correctly so). We pass up out of foo and encounter the catch handler for Xalloc. Stack unwinding is over and control passes to the beginning of the catch clause. The catch clause prints "Xalloc signal caught" and main continues. The message "Exit program" is printed and with the exit from main, the program halts. One important distinction between this implementation and the ANSI specification is that the scope and lifetime of catch handlers are not limited to the try block itself. The catch handler is still active after the try block and in fact has the same lifetime as a local variable declared in the scope in which the cmCatch appears.
______________________________________
int main ( )
cmCatch (Xalloc, xsignal);
if (xsignal)
cout << "Xalloc signal caught" << endl;
else
{
foo ( );
cout << "No signal caught" << endl;
}
cout << "Exit program" << endl;
foo ( ); // Call foo here also
}
______________________________________
Let us change the given example slightly. If we add a call the function foo after the print of "Exit program", we get a different behaviour. The program will execute as before until we call foo for the second time. This will eventually throw Xalloc. The catch handler for Xalloc is still active (until the right brace that closes int main ()) and so the exception is caught. The catch clause is executed and execution continues exactly as before after the end of the if-else blocks. "Exit program" is printed again, foo is called again, and the program loops infinitely. Signals may be grouped in a runtime structure to indicate families of exceptions to be caught. By convention, all signals are defined to be descendants of ccSignal::all. New types of signals can be derived from ccSignal. Commonly, new signal types will carry specific information about the event that caused the throw to occur. Examples of both of these in the VCL can be found in cc2Vector. ccSignal contains two member functions to throw an exception: void Throw () and void error (). Both will print diagnostic information if there is no active catch handler for the signal thrown. If the catch for the signal is a "catch all" or "cmCatch (ccSignal::all, x)" and the exception is thrown with the error method, diagnostics will also be printed. The member function const char* name () retrieves the string used in construction, the function message will print this name on the ostream. Signals derived from ccSignal often redefine message to print other event specific information. The C++ automatic storage class for objects is the language mechanism that is used for implementing the catch handlers. Catch handlers "behind the scenes" create an object on the stack that registers the type of exceptions that will be caught at that point. For simple use, and those usages that similarly conform to the examples given here, the mechanism provides an easy way to specify handlers. Be aware, however, that much mischief is possible. Catch handlers are legal anywhere that multiple declarations are, and have lifetimes and scopes corresponding to objects declared at.sub.-- that point. Pathological examples abound. Example 1
______________________________________
ccSignal Xalloc.sub.-- in.sub.-- strings ("No storage for strings",
Xalloc);
int foo (void)
cmCatch (Xalloc.sub.-- in.sub.-- strings, sxsignal);
cmCatch (Xalloc, xsignal);
if (sxsignal)
// Never reached, Xalloc catch supersedes this catch.
}
______________________________________
Example 2
______________________________________
int foo (void)
for (cmCatch (Xalloc, xsignal); ; x = x->next( ))
// cmCatch is a declaration, so should be legal, right?
// It's actually implemented with two declarations, so this is
// illegal.
}
______________________________________
Example 3
__________________________________________________________________________
int foo (void)
cmCatch (Xalloc, x1signal);
A: if (x1signal)
// handle it
else
// try code
{
cmCatch (Xalloc, x2signal);
B: if (x2signal)
// handle it
else
// try code
C:
} // This block ends the lifetime of the x2signal cmCatch above.
// etc.
D:
cmCatch (Xalloc, x3signal);
// This catch overshadows the x1signal declaration above.
}
Throw origination
Handled by
Start of foo to A:
function chain calling foo( )
A: to B: x1signal handler
B: to C: x2signal handler
C: to D: x1signal handler
D: to end of foo
x3signal handler
__________________________________________________________________________
2. Developer Usage The design of classes that are built to use an exception mechanism have a tremendous dependence on the philosophy and goals used to design the exception system. Section 3-5 discuss these goals and the evolution of the system. This section will concentrate on summarizing the principles that a designer of a class that participates in this mechanism must know. In particular, our mechanism is intrusive on the design of a class. The full requirements and details of class usage is given in section 7. The first decision that a designer must make when looking at a particular class, is what needs to be done in the destructor. What will happen if the destructor is not called? For some, it may simply "leak" memory. For other classes, it may push global collections and references to the class out of sync or not release global hardware resources. In the simplest case, one does nothing. If the class has no destructor, i.e. it does no cleanup, nothing is necessary. An example follows.
______________________________________
class real
public:
Real (double);
operator double ( );
friend ostream operator << (ostream&, const Real&);
// etc.
private:
double value;
};
______________________________________
Note that all storage for the class is on the stack and there are no intrinsic global references, i.e. the class is not placed a global list anywhere (visa vis VCL screenObjects). If the stack is unwound past a Real object, there is no destructor to call. This category of classes is called "simply constructed". For a class that has a destructor that does something (is non empty) the "intrusion" on the source text begins to appear. Consider this case . . .
______________________________________
class String
public:
String (const char*);
.about.String ( );
String& operator = (const String&);
String (const String&);
// etc.
private:
char* storage;
unsigned len;
};
______________________________________
This class needs to be annotated with the information necessary for the exception mechanism to find it when unwinding the stack. The correct definition would look like this:
______________________________________
class String: public ccUnwind
public:
cmObjectInfoDcl;
String (const char*);
.about.String ( );
String& operator = (const String&);
String (const String&);
// etc.
private:
char* storage;
unsigned len;
};
// string.c
cmObjectInfoDef (String);
const ccObjectInfo& String::repInvariant (int) const
{
// Do repInvariant stuff
// See $localDOC/test.frame.txt for description
ccUnwind::repInvariant ( );
return *this;
}
______________________________________
Such a class is called unwind constructed. One further category of class is called "complex" constructed. It fulfills the requirements of unwind constructed classes, but makes necessary special provisions for throws occurring during the construction process, leaving the object partially constructed. For completeness, an example looks like this:
______________________________________
class ScreenShape : public ccUnwind
public:
cmObjectInfoDcl;
ScreenShape (Screen&, double, double);
.about.ScreenShape ( );
ScreenShape (const ScreenShape&);
// etc.
private:
Matrix coordinates;
Xfrom transform;
// etc.
};
// screenshape.c
cmObjectInfoDef (ScreenShape);
ScreenShape::ScreenShape (Screen&, double d1, double d2) :
coordinates (d1), Xform (d2)
// these constructors might fail
{
cmCStart;
// etc.
}
ScreenShape::.about.ScreenShape ( )
{
cmDStart;
// etc.
}
ScreenShape::ScreenShape (const ScreenShape&)
{
cmCCantFail;
}
______________________________________
At this point in designing a class, you should make sure to know the rules in Section 7 thoroughly. Two major limitations must be kept in mind: no destructor may throw an exception, and no "ctor-initializer" may throw an exception. A ctor-initializer is the expression used to initialize a member of a class using the colon, member initializer list syntax. As above:
______________________________________
ScreenShape::ScreenShape (Screen&, double d1, double d2) :
coordinates (foo(d1)), Xform (foo(d2))
// foo(d1) and foo (d2) may not fail.
______________________________________
3. Background Exception handling is a necessary component of Cognex vision processors, which are expected to operate indefinitely without rebooting. The mechanism specified in the C++ reference manual and accepted by the ANSI committee would be ideal, but is not available and is not expected to be so anytime soon. The catch/throw mechanism that we use in our C library is not sufficient for C++ programs, because automatic variables are not destroyed on throws, and suffers from other problems that have resulted in numerous bugs over the years. This new system is intended to address these problems. In the following discussion, I assume that the reader is familiar with either the ANSI standard or our C library mechanism, so that I don't have to define a bunch of basic terminology. The proposed ANSI standard, our C library catch/throw mechanism, and this new system are semantically very similar, although the syntax and some operational details are very different. Each version implements the termination model of exception handling: On detecting a condition that prevents a code fragment from achieving its intended result, control and some information may be passed back to some dynamically determined point, in a currently active block that directly or indirectly invoked said fragment, that has indicated its ability to handle the error. Control cannot subsequently resume at the point of the error. 4. Functional Requirements The following functional characteristics are mandatory: 1) A set of signals can be defined that correspond to the set of errors that can be detected. A throw specifies a specific signal, and a catch specifies either a specific signal or all signals. A catch can determine which signal was thrown. 2) A throw transfers control to the most recently activated catch that is still active and that specifies either all signals or the signal thrown. 3) All automatic variables that were constructed after said catch are destroyed in reverse order of construction, exactly as specified by the language rules. 4) A catch is deactivated whenever the block that contains it is exited. 5) Throws must be permitted in constructors. Quoting from Ellis & Stroustrup, "An object that is partially constructed will have destructors executed only for its fully constructed sub-objects. Also, should a constructor for an element of an automatic array throw an exception, only) the constructed elements of that array will be destroyed." The following functional characteristics are desirable: 6) In addition to a specific signal or all signals, a catch can specify a group of signals. 7) Information can be passed back to the catch in addition to the identity of the signal thrown. 8) A function can be called to determine if there are any active catches, not counting those specifying all signals, that can handle a given signal. 9) An individual compilation unit can define a unique signal without knowing about signals defined by other compilation units (i.e., signals are not defined by integer constants). 10) Very little run-time overhead, in space or time, is required for code that does not do a throw. 11) The mechanism does not depend on language behavior that is implementation dependent, and does not require any modifications to the compiler or its C output if any. 12) The mechanism requires a minimum of explicit action on the part of the programmer (changing "a minimum of" to "no" is a lost cause given #11), and any misuse of such explicit actions will result in compile or run-time error messages. 13) Classes provided by 3rd parties, such as AT&T's Standard Components, that do not follow the conventions required in #12 and for which we do not have the source, can nevertheless be used (presumably by deriving a class that does follow said conventions). 5. Signals A signal is an instance of the class ccSignal or a class derived from it. All instances of ccSignal are static, so that #9 is assured by the uniqueness of static object addresses. All signals in the system are organized into a tree with a single root, where the terminal nodes are specific signals, the non-terminal nodes are groups, and the root is the special group consisting of all signals. The signal tree is a run-time structure that is independent of any class hierarchy of signals derived from ccSignal. By convention, a signal is not a global identifier but rather is a public static member of the appropriate class. For example, the signal that specifies the special group of all signals is ccSignal::all. Throw and other related functions (but not catch) are virtual member functions of ccSignal. An instance of ccSignal contains a printable signal name (i.e. a string) that is generally more descriptive than the identifier used in programs. To pass additional information from a throw to a catch, one derives a class from ccSignal that includes the additional information. ccSignal has a virtual member function that uses any such additional information to print a long message describing the error. This function would be overridden in any derived class. 6. Coding Conventions for Catch and Throw The member functions Throw, error, and willCatch of the class ccSignal correspond to our C library functions cct.sub.-- throw, cct.sub.-- error, and cct.sub.-- willcatch. Use, therefore, is something like this: ccSomeClass::signalName.Throw () ccSomeClass::signalName.error () ccSomeClass::signalName.willCatch () Note that "throw" is a reserved word, so I use "Throw". These can be used anywhere an expression is legal. A catch looks something like this:
______________________________________
{ someType a, b, c;
cmCatch (ccSignal::all, signal);
if (signal)
// signal caught?
{ // catch handler
signal->Throw ( ); // re-throw signal caught
}
else // not needed if handler can't fall through
{ // equivalent to ANSI standard "try" block
someType x, y, z;
}
// catch still active here unless signal already caught
} // catch deactivated
______________________________________
"cmCatch" is a macro that declares its second argument to be of some special type that behaves like a ccSignal*, and installs a catch on the signal or group specified by its first argument. The cmCatch macro is used like a declaration and not like an expression. In the above example, the cmCatch can be thought of as declaring "ccSignal* signal=0"; if a signal is caught, "signal" will point to it. Note that "signal" is not actually a ccSignal*, but is made to behave like one by operator overloading. In the above example, when the catch handler is entered the automatic variables a, b, and c will exist but x, y, and z will have been destroyed. 7. Coding Conventions for Classes In the following, a section of code is said to "fail" if the code terminates with a throw. A throw that is caught within the section of code and does not propagate outside (for example, by being re-thrown) is not a failure. All classes must be simply-, unwind-, or complex-constructed. For a class provided by a 3rd party for which we do not have source and that does not satisfy these rules, one must derive a new class that does. Informally, if you are writing a class with no destructor, and construction can't fail, do nothing special (it doesn't matter if your class has bases or members with destructors, or bases that can fail to construct). If your class has a destructor, but the constructors and members can't fail, then build it according to the rules of unwind-constructed classes. If it has one or more constructors or members that can fail, build it according to the rules of complex-constructed classes. Strictly speaking: A class is called "simply-constructed" if and only if: 1) It has no destructor body (it can have a default destructor). 2) No constructor body or ctor-initializer expression can fail. 3) No constructor body contains the macro cmCStart or cmCCantFail. 4) No member object can fail to be constructed (base classes don't matter). A class is called "unwind-constructed" if and only if: 1) The class ccUnwind is a non-virtual base class. 2) No constructor body or ctor-initializer expression can fail. 3) No constructor body contains the macro cmCStart or cmCCantFail, and the destructor body, if any, does not contain the macro cmDStart. 4) No member object can fail to be constructed (base classes don't matter). 5) It includes the cmObjectInfo macros in the class definition. A class is called "complex-constructed" if and only if: 1) The class ccUnwind is a non-virtual base class. 2) It has a destructor body that starts with the macro cmDStart. 3) All constructor bodies that can fail start with the macro cmCStart; all others start with either cmCCantFail or cmCStart. 4) No ctor-initializer expression can fail (the constructor for objects initialized with ctor-initializers can fail). 5) It includes the cmObjectInfo macros in the class definition. Note that the base class ccUnwind can be declared in any order. These rules are designed to allow the programmer to make tradeoffs between efficiency and safety as appropriate in practical situations. The programmer can, for example, make every class complex-constructed so that there is no possibility of misinterpreting the rules or being subject to changing implementation of member objects, but doing so is probably unnecessarily inefficient. The rules for simple- and unwind-construction are provided to allow more efficient classes when possible, although the rules never require that they be used. If is important to note that, with some exceptions discussed below, one can determine that a class is constructed according to the rules using only information contained in that cl ass, without reference to its bases or members. Thus if information about bases or members is not available or is subject to change, the class is still safe. I will call this the "local unwind construction rule". There are three exceptions to the local unwind construction rule, two minor and one major. The major exception is the need To know if a member object can fail to be constructed. In most cases the programmer should assume that all member objects can fail, and build a class complex-constructed if it has any member objects. This means that a class can be made simple- or unwind-constructed only if its data members are exclusively built-in types (this is common with simple classes). The first minor exception is the need to know if a constructor body can fail. The only reason that this information is not local to the class is that the constructor might call some function, or declare some object, without knowing whether such a call or declaration can fail. There are many cases, however, where it is clear that a constructor body can't fail, for example bodies that are empty except for the macro cmCCantFail. Note that if a constructor has a ctor-initializer, failure of one of those initializations is member object failure, covered in the previous paragraph, not constructor body failure. The second minor exception is that ccUnwind need not be a direct base. There is a danger, however, in relying on another unwind-constructed base class to provide ccUnwind for you, If the implementation of that base later changes to make it simply-constructed, the derived class might break. It would be permissible, even recommended, that a class derived from unwind-constructed bases redundantly include ccUnwind as a direct base, to avoid this danger. Note, however, that the compiler (Cfront 3.0) will not accept ccUnwind as a direct base class if it is also an indirect base class, because any reference to its members would be ambiguous. The compiler issues a warning (not an error) and simply omits ccUnwind from the class. Unfortunately, this is a case where the compiler is wrong--we never refer to any of ccUnwind's members, we simply want it constructed and destroyed appropriately. One has 3 choices: omit ccUnwind, ignore the warning, or make an empty class with ccUnwind as a direct base to avoid the ambiguity: class fooUnwind: public ccUnwind {}; class foo: public fooUnwind, public someClass { . . . }; There is one significant limitation of the above rules that must be understood--there is no way to build a class where a ctor-initializer expression can fail. Again, the member object being constructed by such an initializer can fail, but the expressions used as arguments to the object's constructor cannot. If a member object needs to be initialized based on the result of an expression that can fail, there are two choices: either put the initialization in the constructor body, or, when that is not possible, wrap the expression in a function that catches all signals and takes appropriate action if a signal is caught. Appropriate action might be to initialize the object to some error state, test for that state in the constructor body, and if detected do the throw in the body. If experience shows that this is a common problem, it should be possible to provide a more general solution, similar to the use of cmCStart in constructors. 8. Implementation Overview The actual transfer of control from a throw to a catch, and restoration of the machine environment, is handled by the ANSI standard functions setjmp and longjmp. Thus all implementation-dependent details are handled by standard functions, satisfying #11 and resulting in a portable implementation. The cmCatch macro declares an automatic "catch frame" object, and then does a setjmp. cmCatch must be a macro, because the setjmp must be executed in the block where the catch is established. cmCatch could in principal be an inlined member function if we could guarantee that the function would always be inlined, but the language rules do not require the compiler to do so. The constructor for the catch frame object links the catch frame at the head of a list, called the catch list, and remembers the signal or group to be caught and which unwind-constructed automatic objects currently exist. The destructor removes the catch frame from the list, which must be at the head or a fatal error results. In our C library mechanism, considerable implementation-dependent hacking was used to get catches to be deactivated when functions containing them returned. Use of automatic objects and destructors in C++ neatly solves this problem. One consequence, however, is that catches are deactivated when the block, rather than the function, containing them exits. Although there are cases where some may consider the new behavior to be overly restrictive, I consider it in general to be mandatory--once a block exits its environment (automatic variables) is destroyed and one cannot simply transfer control back into the middle of it. Since the catch frame object must be automatic, steps are taken to prevent unintended use. First, the class name is cc.sub.-- CatchFrame. If ever you see this name in a program, it is an error. Second, operator new and delete are overloaded and made private member functions of cc.sub.-- CatchFrame. These steps should prevent accidental misuse; intentional misuse cannot, as far as I can tell, be prevented. Note that we could have the constructor for cc.sub.-- CatchFrame check to make sure that the object is on the stack, but I consider that unnecessary run-time overhead given the other precautions. Even that step doesn't prevent all illegal uses, such as using cc.sub.-- CatchFrame as a base or member of another object. Now for the tricky part--the construction and destruction of automatic unwind-constructed objects. First, we must have some means for determining if an object is automatic. The means we use is technically implementation-dependent, but should cover any reasonable single-threaded implementation. An object is automatic if and only if it is on the stack. We determine if an object is on the stack by looking at its address. We assume that there is one stack and that it occupies contiguous memory locations. We assume that if function A invokes function B, then all automatic variables in B are closer to the top of the stack than all automatic variables in A, which can mean either higher or lower memory addresses (stack growth direction is unimportant). We make no assumptions about the order of allocation of automatics in a function; in particular, automatics in nested blocks are not necessarily closer to the top of the stack than those in containing blocks. These assumptions allow us to determine an address near the bottom and top of the stack so we can determine if some address is between them. The function "main" declares an automatic, remembers its address, and calls the function userMain, which replaces the functionality of main. This address is considered the stack bottom. A function is defined to determine if an address is on the stack. That function also declares an automatic, and considers its address to be the top of the stack. The rest is simple arithmetic. We considered using a constructor for a static object to determine stack bottom, but rejected it as not guaranteed to work. We also considered using the address of the catch frame at the top of the catch list as an effective stack bottom, but this would require that automatics in a function are allocated in construction order, which is not guaranteed. Next, one must understand exactly how objects are constructed and destroyed. The order of construction is not implementation-dependent but rigidly specified by the language: first virtual base classes in a particular order (details unimportant here), then non-virtual base classes in declaration order, then member objects in declaration order, then the object's constructor body. The order of destruction is similarly constrained, and is the exact reverse of construction. The other important part of the construction/destruction story is access to virtual functions defined in base classes and overridden in the derived class that we are constructing. While the base classes are being constructed, calls to such virtual functions refer to the base class functions, not the overriding derived class functions because the derived class isn't yet fully constructed. At some point after all bases are constructed but before the derived lass's constructor body is entered, the object is switched over to use the overriding virtual functions. This switch point can be before, after, or, I suppose, during the construction of member objects. Although the reference manual does not discuss this, according to Sam the ANSI committee considers the switch point to be implementation-dependent. Normally class behavior is unaffected by the choice of switch point, but here the choice is crucial. In the following discussion I will assume the most general case, that zero or more member objects are constructed before the switch point and zero or more after. In order to understand why key implementation decisions were made, I will describe the operation of unwind-constructed objects in historical sequence: A simple mechanism that did not work, a slightly more complex mechanism built upon the first that also did not work, and finally the current version, somewhat more complex and built upon the second, that does work. The goal is to justify the current design as minimal given #12 above. Furthermore, I believe that the most: important purpose of comments in source code is to explain why design decisions were made, so that modifications won't break the code. The active ingredients in ccUnwind are a constructor, a virtual destructor, and a link pointer for making a list. The constructor checks to see if the object is automatic, and if so puts it at the head of the unwind list. If not, the object is marked by making its link point to itself (null is of course used by the ccUnwind at the tail of the list). The destructor first checks to see if the object is marked as not automatic, and if so does nothing. Otherwise, we search the unwind list for the object and remove it. Note that the object is almost always at the head of the list, due to the language rules requiring destruction in reverse order of construction. One case where this strict ordering can be violated is when temporary objects must be constructed during the initialization of automatic objects. As unwind- and complex-constructed automatics are constructed and destroyed during the normal course of program execution (i.e., no throws), the ccUnwind objects that they contain will be linked and unlinked in proper sequence. No subtle or interesting behavior occurs so far. When a throw is done, we scan the catch list to find the first catch frame that matches the signal being thrown. We then repeatedly destroy the ccUnwind at the head of the unwind list, by explicitly calling its destructor, until the ccUnwind at the head of the list is the one remembered in the catch frame as existing before the catch was installed. (If no catch frame matched the signal, we destroy them all and exit). Since the ccUnwind destructor is virtual, calling it will destroy the complete object that contains it. This simple mechanism works, in most cases, regardless of the position of the ccUnwind in the base class list; regardless of whether the ccUnwind is a virtual base class or not or even if some classes declare it virtual and others do not; regardless of whether other base classes are simply- or unwind-constructed; and regardless of switch point. If we require ccUnwind to be non-virtual, it also correctly handles destroying partially constructed objects in case a constructor does a throw. It does not, however, work if the class contains unwind-constructed members. Consider a class with a bunch of unwind-constructed bases and the direct base ccUnwind, but no unwind constructed-members. The bases will be linked on the unwind list in declaration order. While the bases are being constructed, and therefore before the switch point, the ccUnwind virtual destructors access the destructors for the base sub-objects, so if a throw is done while constructing a base only the constructed sub-objects will be destroyed. After all bases are constructed, and after the switch point, all of the ccUnwind virtual functions access the destructor for the derived, complete object, so calling any one of them (i.e., the one at the head of the unwind list) will destroy the entire object and unlink all of them in the process in the right order. Now add unwind-constructed members. These will be linked on the unwind list after the base classes due to the required order of construction. Since they are not base objects, however, their virtual destructors cannot access the destructor for the complete object. If we follow the current algorithm and destroy the object at the head of the unwind list, we will make 2 fatal errors: first, the order of destruction is wrong, because the member object is destroyed before calling the destructor body of the complete object; and second, the members are destroyed twice. Clearly, we must determine if an object is a member of a previously constructed object. This information could be used either at construction time, to prevent linking the member on the unwind list, or at throw time, to skip over member objects. I considered two ways to determine object membership: maintain an object construction level count that is incremented when constructing ccUnwind and decremented in constructor bodies; or check if an object's address falls between addresses determined by a previously constructed object's base address and size. The latter method is somewhat implementation-dependent, but not really more so than the method used to determine whether an object is automatic. The former method has a more serious disadvantage--it requires that a class with one or more unwind-constructed bases, and one or more unwind-constructed members, be unwind-constructed even if it has no constructors and destructor itself. When one is writing a class, one does not know in general whether bases and members are unwind-constructed, and such details may change over time. I consider it highly desirable that the decision to build a class as simply- or unwind-constructed be made independent of the status of bases and members. To implement membership checking by address, I defined an abstract base class ccObjectInfo, containing public virtual member functions that provide information about an object derived from it, such as base address, size, and a printable name. ccUnwind is then derived from ccObjectInfo, which adds no run-time space overhead (if ccObjectInfo and ccUnwind are non-virtual, the right choice anyway). A macro, cmObjectInfoDcl, is provided that allows any class to override ccObjectInfo's virtuals in the class definition. The actual definitions are placed out of line in with the cmObjectInfoDef macro. This redefinition is required in unwind-constructed classes, and I further recommend that it be included in all non-trivial classes we write. The member functions created by expanding the macro are not declared virtual, so that the macro will never add storage for a vtbl pointer to a class that doesn't already have one by virtual of having ccObjectInfo as a (possibly indirect) base. For those classes where the `virtual-ness` is desired, they can be declared so by hand in the root of that hierarchy. ccObjectInfo has benefits beyond exception handling--it has been very useful in debugging, because one can identify objects at run-time without knowing their type at compile time and provides print/dump facilities throughout the VCL. The following statements are true given the currently specified mechanism (proof left as an exercise): 1) Testing an object for membership at construction time (to avoid linking on the unwind list) only works if the member object is constructed after the switch point. 2) Testing an object for membership at throw time, assuming all objects are fully constructed (to skip over the destructor), works regardless of switch point. 3) Destruction of partially constructed objects (constructor does a throw) only works if all member objects are constructed before the switch point. Clearly, the current mechanism will work only if we test for membership at throw time and if all member objects are constructed before the switch point. Cfront 3.0 constructs member objects after the switch point, and anyway it's implementation-dependent as discussed above. We must find some way to effectively prohibit throws in constructors and yet satisfy functional requirement #5. The answer is to allow them, but install a catch of ccSignal::all at the beginning of every constructor so that the throw does not propagate outside of the constructor. If a signal is caught it is remembered, and then we go through the motions of constructing the rest of the object, postponing the throw until it is safe to do. Going through the motions means allowing construction to continue, but arranging that each constructor body returns immediately without doing anything. Once the object is "fully constructed", the throw of the remembered signal will cause it to be destroyed immediately. Once again we go through the motions of destruction, until we reach sub-objects that are fully constructed at which point we switch real destruction back on. This is all done with the cmCStart and cmDStart macros as described in the complex-constructed rules above, and is actually much simpler and faster than it might sound. A static counter "errorSkipLevel" is defined. If a signal is caught by the catch installed by the macro cmCStart, errorSkipLevel is set to 1. If the object is not a member of another object, the signal is re-Thrown immediately, otherwise the signal is remembered and the constructor returns, allowing "construction" to continue. On constructor entry, errorSkipLevel is tested. If it is zero construction continues normally. If not it is incremented, and if the object is not a member of another object the remembered signal is thrown, otherwise the constructor returns. On destructor entry, errorSkipLevel is also tested. If it is non-zero it is decremented and the destructor red, urns immediately. This mechanism works regardless of: switch point, but for subtle reasons. If a member object constructed before the switch point does a throw, it will not be considered a member because the ccObjectInfo virtuals in the base classes of the complete object don't yet know about the complete object. "Construction" will not continue--all sub-objects constructed so far will be destroyed immediately. This works because the virtual destructors in the ccUnwind base classes also haven't been switched yet to destroy the complete object. If a member object constructed after the switch point does a throw, it will be considered a member, arid we will "construct" the rest of the object. When the postponed throw is finally done, we will correctly skip the member objects at the head of the unwind list, and use the destructor of a base to destroy the entire thing. Note that once the switch point has been passed during construction, the object must be switched back to destroy the bases or incorrect behavior will result. The code to switch back exists only in the destructor for the complete object, so calling the complete destructor is mandatory. Note that while going through the motions of construction and destruction, simply-constructed objects will actually be constructed, and unwind-constructed and 3rd party objects in a complex-constructed wrapper will actually be constructed and destroyed. This should be OK. I struggled hard but in vain to avoid the need to use cmCStart and cmDStart, and to a lesser extend ccObjectInfo. Once I gave up trying, I began to rationalize. First, most classes will be simply- or unwind-constructed. Second, the discipline of writing ccObjectInfo, cmCStart, and cmDStart into complex classes gives us almost complete freedom to modify the construction/destruction behavior of such classes in the future without having to edit all of our header files. For example, if we need some action to happen on normal exit from constructors, we make a class with a destructor that does the action, and declare an object of that class in cmCStart. 9. Unresolved Issues Destruction of partially constructed automatic objects in general cannot be handled entirely by any exception handling mechanism. The above scheme guarantees that an object's destructor body will not execute unless its constructor finished, i.e. returned normally. If, for example, the constructor's job is to allocate 2 heap objects and we run out of memory on the second one, the constructor itself must insure that the first block is deleted. This situation is similar to the case of a function that wants to allocate and return some heap object, but must insure that the object is deleted if the function is aborted by a throw. These cases can be address by the use of a set of handle classes; see the file handle.H. A related but perhaps more serious problem is the destruction of partially constructed non-automatic objects. Destruction of fully-constructed non-automatic objects must be the complete responsibility of the programmer, but if construction of such an object fails, how can the programmer know what sub-objects were constructed, and what could he do about it anyway? Perhaps such an object must be considered automatic until it is fully constructed, at which point it is somehow removed from the unwind list. Finally, I have not considered asynchronous throws, such as keyboard or timer aborts. We have two choices: 1) wrap software lock and unlock functions around sections of code that cannot be interrupted; and 2) check for such conditions at various safe points in the code. With #1, programming mistakes can fatally destroy system integrity; with #2, infinite loops can be fatal. The current system is undefined with regard to asynchronous throws and the user is advised to beware. 10. Caveats Because signal objects have static storage class, care must be taken during static initialization to avoid using signals defined in other compilation units. This is because C++ does not define the order of execution of static initializers among different compilation units. During initialization of module A, there is no guarantee that a particular signal object of another module B has been initialized, so any exception activity involving module B's signal object might fail. Shapes The following files are discussed: $onsightDEFS/shapes.H $onsightSRC/geom/shapes.C Shapes.C consists of a number of classes that manipulate and draw basic shapes. Although it's used primarily by planeFigures.C to make handled region classes, shapes are general purpose classes. To make the classes as efficient as possible they do not use any unnecessary inheritance. Some classes derive from ccUnwind (and use cmObjectInfoDcl) because they are complex constructed and must follow the proper exception handling rules. Shapes do not derive from a common base class because the differences between the classes does not warrant it. The same names are used between classes to indicate the same functionality. Classes defined in shapes.H are for applications where instances of specific shapes are needed. The classes in planeFigures.H are used when generic shapes are specified. Shapes are stored as real numbers (doubles, ccRadian, . . . ) and most parameters can take on any value. Points, Lines, etc . . . are represented exactly (or as exact as a 64 bit double can). The drawing routines in graphics.C convert the real values to integer for drawing in a cip.sub.-- buffer. Vector manipulations are used heavily in all of the code to simplify its design. The reader must be well (I repeat WELL) versed with vector manipulations to follow what the code does. The source contains comments where appropriate. Q: Why are the default constructors empty? A: In this implementation you can create an uninitialized instance of any shape. There is nothing to stop you from using this object before it is initialized. This is the same behavior as any built-in C++ type. I thought about adding a mechanism to detect the use of an object before it is initialized. I decided that it wasn't worth it for a couple of reasons. First, most routines use the handled versions of these classes (for instance, ccRectHdl instead of ccRect). Second, making an object bullet-proof is difficult for classes like ccPoint since it derives from ccFPair. ccRect throws an error if an operation is attempted on an uninitialized object. This behavior was prompted because the intersection of two ccRect's can produce a null ccRect. Q: How are argument calling and return types decided? A: A class typically takes as an arguemnt the base class object so the function works for either the base class or derived class. As far as return types, the derived class is returned so its value can be used either way. For instance: ccPoint ccLine::intersect (const cc2Vector&) const; can be called with either a cc2Vector or ccLine and returns a ccPoint or ccFPair. Q: Why does ccLine derive from cc2Vector and ccPoint doesn't? A: Pattern of usage. Points are used extensively throughout the system and are designed to be very efficient, Lines are less frequent so deriving them from cc2Vector makes sense because of the amount of code reuse possible, Work TBD: Remove comments around repInvariant when they are all complete. Extend checks for ccEllipse Check the construction of an ellipse from its coefficients. Make sure that the desired degenerative forms are allowed. Member functions available with most/all classes double distToPoint (const ccFPair&) const; Compute the distance from the shape and the specified point. The distance is zero only along the boundary of the object. int within (const ccFPair&) const; Returns true if the specified point is on or within the shape. This member function is available for ccRect, ccCircle, ccEllipse and ccGenRect (and ccEllipseArc through inheritance from ccEllipse). ccRect encloseRect () const; Returns the enclosing rectangle of the shape. This member function is available for ccRect, ccCircle, ccEllipse and ccGenRect (and ccEllipseArc through inheritance from ccEllipse). void draw (const ccDisplay&, ccGcw=ccGcw::deflt) const; Draw the shape on the indicated display (ie: cip.sub.-- buffer). ccPoint A ccPoint is stored as a ccFPair (actually, ccPoint derives from ccFPair so all ccFPair member functions are available to users of ccPoint). There are no restrictions on where a ccPoint can be located. Points can be constructed from two reals, a ccFPair (and hence another ccPoint) or a cc2Vector. ccPoint map (const ccCoordXform& c) const; Map the point under coordinate transformation into a new ccPoint. ccPoint midPoint (const ccFPair&) const; Computes the location of the midpoint between two ccPoints or a ccPoint and ccFPair. ccLine connect (const ccFPair&) const; Computes the line that passes through two ccPoints or a ccPoint and ccFPair. If the two ccPoints are equal, a cc2Vector exception gets thrown (cc2Vector::noAngle) ccLine bisect (const ccFPair&) const; Computes the line that is the perpendicular bisector between two ccPoints or a ccPoint and ccFPair. If the two ccPoints are equal, a cc2Vector exception gets thrown (cc2Vector::noAngle). ccLine ccLine performs many subtle operations on lines. The source code contains a description of the problem and how to solve it. A line is stored as a vector from the origin to a point on the line such that the vector is normal to the line. Polar coordinates can easily store the representation of any line with one large exception. Any line that passes through the origin is stored as a vector of radius zero and appropriate direction. The direction can be one of two values (separated by 180 degrees from each other). cc2Vector supports the notion of a pseudo-null vector to handle this need. However, many member functions (such as map()) must guarantee that their formula works for pseudo-null vectors as well as for the general case. ccLine derives from cc2Vector so access to all cc2Vector member functions are available. Construction of a ccLine requires a cc2Vector (or another ccLine). ccLine map (const ccCoordXform& c) const; Map the line under coordinate transformation into a new ccLine. In general, mapping a ccLine can produce a ccPlaneFigure. However, a simplification is made to guarantee a ccLine is created. See the source code comments for more details. ccRadian angle (const cc2Vector&) const: Return the angle between the line and the specified vector (or line). The angle is measured from the vector to the line. ccRadian angle () const; Return the angle of the line. This is redefined because the above angle definition hides the cc2Vector::angle() definition. ccPoint intersect (const cc2Vector&) const; Computes the intersection point of two lines. The signal ccLine::intersectParallel is thrown if the lines are parallel. ccLine parallel (const ccFPair&) const; Compute the line parallel to the current line that passes through the specified point. ccLine normal (const ccFPair&) const; Compute the line perpendicular To the current line that passes through the specified point. ccLineSeg ccLineSeg stores two points (ccPoint's) which define the segment. A degenerative line is one whose line segment is actually a point. ccPoint p1 () const; ccPoint p2 () const; Returns the first or second stored point associated with the line segment. The ordering of the points is not important. ccPoint p1 (const ccFPair& p); ccPoint p2 (const ccFPair& p); Changes the location of one of the end points of the line segment. ccLineSeg map (const ccCoordXform& c) const; Map the line segment under coordinate transformation into a new ccLineSeg. int degen () const; Returns true if the ccLineSeg describes a point. double distToPoint (const ccFPair& p) const; Compute the distance between the line segment and the specified point. Depending on where the point is located, the return value can be the distance from the point to one of the end points or the distance along the perpendicular line passing through the point. ccLine line () const; Compute the line which passes through the line segment. The exception degenLineSeg is thrown if the line segment actually describes a point. ccRect ccRect stores the lower-left point and the width and height of the rectangle. Negative width and height values cause the lower-left point to be moved to make the values positive. A zero width and height describes a degenerative point. The intersection operator (&) forces the class to have a level of protection against access of an uninitialized ccRect. The intersection of two non-overlapping ccRect's is a null ccRect. Trying to access or manipulate a null ccRect causes a nullObject error. I was sorely tempted to punt this issue entirely and return an uninitialized ccRect. int isValid () const; Returns true if the ccRect is not: null and is initialized. ccPoint ll () const; ccPoint lr () const; ccPoint ur () const; ccPoint ul () const; Return the various corner points of the rectangle. ccLineSeg lSeg () const; ccLineSeg rSeg () const; ccLineSeg tSeg () const; ccLineSeg bSeg () const; Return the various line segments which make up the rectangle. ccFPair sz () const; Return the width and height as a ccFPair. ccGenRect map (const ccCoordXform&) const; Map the rectangle under coordinate transformation into a new ccGenRect. int degen () const; Returns true if the ccRect describes a point. ccRect enclose (const ccRect&) const; Computes the minimum enclosing rectangle of two ccRect's. This is not the union (which is normally not a rectangle). ccCircle ccCircle stores the center of the circle and the radius. Although a circle is nothing but a special case of ccEllipse, the member functions are easy and execute quicker than ccEllipse. A negative radius value causes a badRadius exception to be thrown. ccCircle derives from ccUnwind because the constructor can fail (for a negative radius). ccPoint center () const; double radius () const; Return information about the current circle. ccEllipse map (const ccCoordXform& c) const; Map the circle under coordinate transformation into a new ccEllipse. This function lets ccEllipse do all the work. ccEllipse A ccEllipse stores its internal state in geometric form (ie: center, major/minor radii, rotation). However, an ellipse can be generated using multiple descriptions. Consult the source for more documentation. Geometric Description The geometric description consists of the center, major/minor radii and rotation. The ccEllipse constructor takes a ccEllipseGeom as argument which contains the parameters. The constructor will throw ccEllipse::badGeom if either radii is negative. A degenerative ellipse (a point) is allowed mainly because a ccGenRect uses ccEllipseArc's for the rounded corners. If these rounded corners are actually square, the ellipses are points. Unit Circle Description This description consists of the center and a matrix. The matrix contains the transformation necessary to turn the coordinates of the ellipse into the unit circle centered at the origin. A degenerate ellipse (both radii=0) is formed if the matrix determinant is 0. The ccEllipse constructor takes a ccEllipseUnit as argument. Coefficient Description The general form is: ax.sup.2 +by.sup.2 cxy+dx+ey+f=0 where: c.sup.2 -4ab<0 (See CRC Math Handbook 27th Edition, pp 196-197) and: An ellipse rather than a parabola or hyperbola is defined. If an error is detected during construction, ccEllipse.badCoef is thrown. The ccEllipse constructor takes a ccEllipseCoef as argument. The constructor will not construct a degenerative ellipse. ccEllipse derives from ccUnwind because the constructors can fail. ccEllipseCoef getCoef () const; ccEllipseGeom getGeom () const; ccEllipseUnit getUnit () const; return a description of the ellipse in any of the three supported formats. ccPoint center () const; Return the center of the ellipse. int degen () const; Returns true if the ellipse is degenerate (both radii=O). ccEllipse map (const ccCoordXform& c) const; Map the ellipse under coordinate transformation into a new ellipse. double distToPoint (const ccFPair& p) const; This computation is only approximate for a ccEllipse. The distance is defined as from the point to the ellipse along the line passing through the center. void dump (ostream& out, int i=0, int j=0) const; Display the ccEllipse in all three representations. ccEllipseArc A ccEllipseArc derives from ccEllipse and contains start/stop phase angles. Consult the header file for more information on how the phase angles can change under coordinate transformation. When the start and stop angles are equal the entire ccEllipse is used. ccEllipseArc derives from ccEllipse. ccRadian phi1 () const; ccRadian phi2 () const; Returns the current angles that make up the arc. ccEllipseArc map (const ccCoordXform& c) const; Map the ellipse arc under coordinate transformation into a new ellipse arc. This operation is much more complicated than it appears because the unit circle representation is overdetermined by one degree of freedom. This can cause the not only the ellipse but the phase angles to change. ccGenRect ccGenRect consists of a ccPoint (center), ccLineSeg's (outline) and ccEllipseArc's (rounded corners). In a manner similar to the unit circle representation of ccEllipse, ccGenRect uses a unit rectangle description to generate a ccGenRect (along with center and corner roundness). The geometric construction takes the center point, major/minor radii, rotation and corner roundness. A ccRect and ccEllipse can be converted into a ccGenRect. The exception badRadius is thrown if any radius<=0. This includes the construction of a ccGenRect from a ccRect. ccGenRect derives from ccUnwind because the constructors can fail. ccPoint center () const; cc2Matrix U () const; ccFPair radii () const; ccRadian orient () const; ccRadian skew () const; ccFPair round () const; ccFPair uround () const; Return parameters that describe the current ccGenRect. ccGenRect map (const ccCoordXform&) const; Map the ccGenRect under coordinate transformation into a new ccGenRect. The map() function is very similar to that for ccEllipse (ccEllipse is a special case of ccGenRect). Handles Overview Handles are a set of classes that provide a shared representation to classes that otherwise may not be able to rely on the intrinsic C support for stack objects and pointers. They provide a shared representation, allowing for fast copy and assignment operations especially for large objects. Handles also allow multiple references to the same objects, avoiding the dangling reference problem. Handles are a VCL analogue to C pointers in that they refer to data, copies of a particular handle refer to the same data, and so do handle arguments. Handles are references, so handled objects exhibit copy by reference semantics in function definitions, not copy by value. Handles supplant the creation, assigment and copy operations of a class. The system requires a class that holds the actual representation (a .sub.-- rep.sub.-- class) and a class that forwards the operations on the rep class from the reference (a .sub.-- handle.sub.-- class). Only the appropriate handles may access the rep class, no client of a handle class may ever see the rep directly. By convention the rep class tag name is the type name prefixed by "cc.sub.-- "and the handle class's is the type name prefixed by "cc". Note that only the handle class's name is visible to the client. In particular, no access to the rep class is allowed, outside of the handle class. There is no "back door" to the representation of the type. Handle memory management is provided by reference counting on the representations. When a representation no longer is accessible through any valid handle, it is destroyed (finalized). Objects of the rep class must have lifetimes across arbitrary function call stack frames. The cmBlock{Store,Def} macros are provided for the simple cases-no heap memory used in the rep class, etc. In any case the rep object must be allocated anywhere you create one in the implementation of the rep or handle classes with the member function alloc. Further, all finalization activity must be enclosed in the cleanup member function. Use of the block storage methods is highly recommended until the author is very familiar with their operation. Objects of the handle type may have an unitialized state. This can be tested for explicitly. Handle objects typically contain no data outside of that inherited from the base class; almost all intelligence for the operations of the type is enclosed in the rep class, together with the data representation for the type. Operations Handles supply only two operations beyond taking care of copy, assignment, and the usual memory bookkeeping: the member function int isInit() and the right arrow selector operator, `.fwdarw.`. One operation is available as derived from ccHandleBase, int refc(). The right arrow selector is provided to access members and data from the rep class. As expected according to C++ rules, the only names that can appear on the right hand side of this operator are those that are member names of the rep class. These follow all access rules of the rep type.
______________________________________
class cc.sub.-- MyType {
public:
void foo ( );
private:
void bar ( );
};
// ....
ccMyType batman;
// ... initialize batman;
batman->foo( );
// calls cc.sub.-- MyType::foo on the data
// batman refers to.
batman->bar( );
// error, no access to cc.sub.-- MyType::bar
______________________________________
If the handle is not initialized and the right arrow selector is invoked on it, the signal ccHandle<rep class>::uninitialized, is thrown. This operator is the primary access to the rep class and is used to access the members explicitly. Members that are called implicitly require forwarding from the handle type itself. These fall into three categories: constructors, operators (as in `operator >` or `operator unsigned long ()`) and signals that the rep class might throw. The isInit () function is supplied to allow checks for a valid handle.
______________________________________
ccMyType batman;
//...
if (batman.isInit( ))
// handle referes to valid data
}
else
// batman is uninitialized
______________________________________
The refc operation returns the current reference count. This will always be at least one, signifying the reference through which the member was invoked. It is supplied as expository information, possibly of use in profili | ||||||
