page 2  (4 pages)
to previous section1
3to next section

template <class T> class Ptr {
int oid;
T* object;
public:
T* operator ->() {
if( object == NULL ) object = fetch( oid ); return object;
}
// etc.
};

Figure 1: The smart-pointer mechanism.

When a smart-pointer to a Foo needs to load its referenced object, it cannot assume the object to be a simple Foo | it may be an object of some class derived from Foo. Therefore, when an object is saved, the first thing written to store is the class identifier so that, on loading, the actual class can be determined before loading the object's body.

The object-base access mechanism is the smartpointer. A smart-pointer is intended to be used as if it were a real C++ pointer, but invisibly performs extra work (if necessary) to retrieve the referenced object from secondary store. Just as pointer X* can be declared for any class X, it should be possible to declare a smart-pointer for an arbitrary class X. This is achieved by a generic class construct. Rather than use C-like macros, the system uses a proposed C++ language extension, the template, as described by Bjarne Stroustrup in [8]; as there is currently no compiler that supports templates, this is achieved using a public-domain template preprocessor (see Acknowledgements").

The generic smart-pointer is described by the class template Ptr, so that a smart-pointer to an object of class X is declared as Ptr<X>; this variable, like an X*, can point to an object of class X or any class derived from it.

A smart-pointer is used to access the database via the overloaded -> operator, as shown in Fig. 1. The smart-pointer refers to the object by OID. The fetch() function first searches a table of objects loaded by the running program, and only searches the secondary store if the object is not in local memory; the address of the object is retained by the smart-pointer for fast access on the second and subsequent dereferences.

Objects should always be referenced by smartpointers. Direct use of the address of the referenced object should be avoided, since if the object were to be saved to store and the local copy deleted it could not be determined where the address was in use. The address would become invalid and could cause errors if it were used. For this reason the C++

unary * operator is not provided for smart-pointers.

There is also a Set class template to allow sets of smart-pointers to be maintained. This supports basic set operations such as joins, unions and iteration.

Partitioning and Reuse

Practical (and intellectual) manageability of a design is achieved, with or without computer aid, by reduction of complexity through partitioning and reuse.

Partitioning involves the representation of a section of design as a single interface at a higher level, so that the implementation of the section can be hidden at the point of use. Hence hierarchical levels of abstraction/resolution are formed.

Reuse involves the definition of design sections as types, so that they can be designed in one place and used many times in many places. Since only the interface is required at the point of use, the implementation of the design section need not be replicated. For example, an adder schematic may be designed using logic gates, and used as a rectangular schematic symbol in other designs without duplication of the gate-level implementation.

A type may provide more than one possible implementation | for example the adder may have carry look-ahead and ripple-carry alternatives. An interface can obviously have no more than one implementation, but it does not have to be bound to a particular implementation at all; it can be left parameterised[1]. The explicit binding of an implementation to an interface is called static configuration.

The bound implementation itself may contain parameterised interfaces, and it may sometimes be appropriate to have these configured differently for different uses of this implementation. This is called dynamic configuration, and involves the implementations being attached not to the interfaces, but to the enclosing static configuration; any number of levels of parameterisation may be descended. For example, interfaces A and B both represent a 16-bit adder design. A is statically configured with an implementation that uses two parameterised 8-bit adders; it is required to be fast so the two constituent adders are configured with fast implementations. B is statically configured with the same implementation, but this time the 8-bit adders are configured with compact implementations so that B is compact.

A general design section is modelled by an object of class Type. This contains the data for the design itself (and its history), and details of the internal connectivity of the interface in the form of InternalPort objects. When a design section is to be used somewhere, the Type creates an object of class Interface. This carries external connection details in the form of ExternalPort objects created by, and mapped back to, their corresponding InternalPorts. This mapping may involve translation, for example between an 8-bit bus and eight single lines.

A statically configured Interface also contains a reference to a ConfigBlock object, which in turn may reference other ConfigBlocks for dynamically configured subcomponents. Each ConfigBlock