Keywords: creation, object, value, reference, equal, copy, clone
An object is an instance of a class, and a class is a set of variables and their routines. Each object has its own variables, and all objects share the routines of their class. An object is created by the creation command !!. A creation routine can be called if needed, to give the variables a more useful value; if the default values are useful, no creation routine is needed.
The value of a variable may be a simple value that can be used immediately, or it may be a reference to an object. For two references, we must distinguish between the same reference value (point to the same object) and the same content value (different objects, same content); two copies of this book, for example, contain the same content but are not the same object.
Objects are the second fundamental way to reuse code in Eiffel. A class is defined as a set of variables and a set of routines, and this class defines the template or appearance of an object. When an object is created, it has its own set of variables, and can use the routines defined in the class that set and use these variables. The class definition is written once, and then objects are created. Once a bank ACCOUNT class has been defined, for example, we can create and use 10,000 actual bank accounts, because they all behave in the same way. This is possible to do because of two things: object creation and class encapsulation.
Object creation is presented below in several stages. First, the code in the client class is shown that defines the object template (the class), then the code in the supplier class used to create an object is shown. Second, the data structure of the object, after it has been created, is shown. Finally, the mechanism for how the code creates this data structure at run-time is shown.
The creation command is two exclamaton marks !!. The term "exclamation mark" is cumbersome to say, so the term "bang" is often used in computing instead, so we can say the creation command as "bang bang". The code in the client class declares an object and then creates it with a creation instruction, a creation command followed by the name of the object. The type of object is found from the declaration, the class definition from the type, and the variables and routines are found in the class definition. To create two points, for example, we need the following code in the client classs.
class LINE creation make feature left, right: POINT make is -- create two points do !!left !!right end -- make ... end -- class LINEThis partial class definition shows that a LINE has two attributes of type POINT, that are the left and right ends of the line (we can't name them start and end, because end is a reserved word (see Appendix B)). The make routine in class LINE consists of two creation instructions (of the form !!name). When the make routine is executed, two objects of type POINT are created. Eiffel finds the definition of class POINT, creates two point objects and gives the basic variables in each point the relevant default values. A partial defintion of the supplier class POINT is.
class POINT
feature x, y: REAL ... end -- class POINTAn object of type POINT has two attributes, two real numbers with names x and y, that store the location of the point. The default value of a real number is 0.0. The internal data structure and initial values of a point are thus
Consider the first creation instruction in the make routine of class LINE, !!left. After the point has been created, the identifier left refers to a composite object, a point, where a point consists of two real numbers. The identifier left is thus a reference, because it refers to an object with its own internal structure. The initial or default value of a reference type is Void, a special value that indicates the identifier does not (yet) refer to any object. When an object is created, the location of that object is placed in the reference variable. Formally, the value of the identifier left is a pointer to some location in memory, the location where the object's data is stored. After the first point has been created, the data structure of a line is
and the value of the identifier left is a reference to memory, shown here in hexadecimal notation. A second point is created by the second creation instruction, and after both creation instructions have been executed the make routine terminates. The structure of a line after termination of the creation routine make in the client class LINE is shown below.
All of this is initiated when a client of LINE creates a line, or LINE is the root class of the system. When Eiffel starts executing a system it creates an object for the root class, and then executes the creation routine make in the root class. The creation routine can then create other objects, and these in turn create other objects, as needed. While the final data structure of the system can be very complex, it can be found by drawing a data structure diagram from the data declarations in each class and then tracing out the client-supplier links for the reference types.
The final part of the mechanism is the creation procedure. The name of the creation procedure for a class is written under the creation keyword, at the top of the class after the class name; by convention, the name of a creation routine is make. A creation routine has a creation policy, that lists the classes who can call the routine as a creation routine. The format of a creation clause is
creation {CLASS} nameIf no class is listed after the keyword creation, the creation policy is ANY; like export, multiple classes may be listed in the policy. A class may have multiple creation routines, in which case they are all listed under the creation keyword, separated by commas. If different classes can create an object using different creation routines, then the creation clause is repeated for each creator class.
A creation routine is used if the default values are not enough. In the class POINT, for example, we might add a creation routine to read in the location of the point from the user when the point is created, instead of always creating a point at location (0.0, 0.0). If the default values are good enough, then no creation routine is needed. If there is no creation routine, then the creation keyword is omitted from the class listing. If there is a creation routine, then it must be used when the object is created.
A creation routine make has been added to class POINT in the listing below, that reads in two values from the user and stores them in the x and y attributes. A good convention in writing creation procedures is to have only routine calls in the procedure. This way the make routine can be changed as needed to use more or different routines, without re-writing any of these specific called routines. The POINT creation routine thus looks like:
class POINT
creation make
feature x, y: REAL
make is -- read the x and y values from the user do get_x get_y end --make get_x is -- read the x value from the user, store it do io.putstring ("Enter the x value: ") io.readreal x := io.lastreal end -- get_x
get_y is -- read the y value from the user, store it do io.putstring ("Enter the y value: ") io.readreal y := io.lastreal end -- get_y
... end -- class POINTTo use the creation routine, the name of the routine is added to the creation instruction. A client of class POINT calls the creation routine by using "dot notation": a dot (fullstop, period) is written after the object identifier, followed immediately by the name of the routine (no intervening space). The code to create a line at a specific location, by creating two points at specific locations, is contained in the make routine of the client class LINE:
class LINE
creation make
feature left, right: POINT make is -- create three points do !!left.make !!right.make end -- makeA creation routine can be used as a normal (non-creation) routine simply by omitting the creation command !!. In this case, for example, we could create a point (at some location) and then re-initialisethe same object so it has another location:
!!left.make -- create a new object called left
left.make -- read in new values for x and y, same objectThe same object (left) is here used in the second line with no creation command, so no new object is created; the x and y values of the object created by the first line are used and changed by the second line of code.
When an object is created, a series of things happen. First an area of memory for the new object is allocated by the operating system, then the attributes in the new object are set to their default values. The object's creation routine is then executed if it exists, and finally a pointer to the allocated storage is attached to the object identifier in the client. An object has to be declared before it can be created, so Eiffel knows how much storage to allocate for an object of that type. Creating an object results in the following events:
1. Allocate storage for the attributes of the object. For the class POINT, there are two real-valued attributes, so enough storage for two REAL numbers is allocated.
2. Set the attributes to their default values. When attributes are created, they are set to a default value. INTEGER, REAL, and DOUBLE numbers are set to zero initially, a CHARACTER to the null value (''), a BOOLEAN to the value false, and a reference variable to Void.
3. Run the creation routine if it is defined. The creation routine usually sets the attributes of the object to some more specific value. In the POINT class, for example, the make routine sets the attributes to the initial location of the point, wherever that location might be.
4. Set a pointer from the name to the storage. Every attribute has a value. For the basic attributes (INTEGER, REAL, CHARACTER, BOOLEAN), this value is the stored value of that field. For objects, the value is a pointer to the allocated storage for that object.
There are three
variants of this process:
variant | calling code | events | |
create object, no creation routine | !!name | 1, 2, 4 | |
create object, with creation routine | !!name.make | 1, 2, 3, 4 | |
change object, use creation routine | name.make | 3 |
To really understand
the syntax and mechanism of object creation, you need to understand the
words used to describe each part of the object creation code. The words
and their code are:
creation command | !! | |
creation instruction | !!identifier | !!identifier.make |
creation keyword | creation | |
creation routine | make is ... |
If you try to use an identifier that does not refer to anything (usually because you forgot to create it) then Eiffel flags an error for using a "Void reference"; formally, a void reference is found when you try to use a reference type whose value is Void. An object of a basic type need not be created, but an object of a reference type has to be created or assigned a value.
Common error: Try to use !!object form when a creation routine is defined for the class.
Error code: VGCC (5)
Error: Creation instruction should include call, but does not
What to do: Since the corresponding base Class lists creation procedures, use form of Creation instruction which includes call to one of them
Common error: Try to use a creation routine without a creation export
Error code: VGCC (6)
Error: Creation instruction uses call to improper feature
What to do: Make sure that feature of call is a creation procedure, is not 'once', and is available for creation to enclosing class.
To use an object,
you must do four things:
1. Define the supplier class | class POINT ... |
2. Declare the object in a client | p: POINT |
3. Create an object in the client | !!p.make |
4. Use the object in the client | p.move (1.0, 1.0) |
Common error: A Void reference when you try to use the identifier (step 4) without creating the object. Your system compiles, but then crashes at run time when Eiffel tries to use the identifier. The value of the identifier is Void, so the identifier does not refer to any object.
4.2 Calling
a feature from a client
A feature is an attribute or a routine. A feature can be called from within the same class, by writing its name and arguments. A feature can be called from a client class, by writing the name of the object, a dot, and the feature name and arguments. The action is identical in both cases: if the feature is an attribute then its value is returned, if the feature is a function then its code is executed and its value returned, and if the feature is a procedure then its code is executed.
The general form of a feature call is object.feature (pronounced "object dot feature"). If there is a feature call without an object, then Eiffel assumes the current object is being used and looks inside the current object for the feature definition. A call within the same class thus has the implicit form Current.feature; the reserved word Current denotes the current object.
When a feature is called, the Eiffel compiler looks at the object on which it is called. The name of the object is located to the left of the feature call, on the left side of the dot, or is Current. Given the name, Eiffel can find the type of the object from the variable declaration. Given the type, it can find the class definition. Given the class definition, it looks inside the class for a feature with that name. There is never any confusion; a feature is called on an object of a some type, and that class must contain a feature of the correct name. The sequence of events for the feature call object.feature is listed and diagrammed below.
1 .find the object to the left of the dot; if there is no dot, use Current 2 find the class of this object from the declaration 3. find the feature defined in that class definition 4. execute the feature on the object
It is possible to nest feature calls, so that a feature is called remotely by a class that is not a direct client; the general form of the call is "object.feature1.feature2...". A remote call such as this is evaluated left to right. The first feature is called on the object, and returns a value. The second feature is then called on this value, and returns another value. The third feature is then called on this value, and do on. The features within the sequence must be queries, so that an object reference is returned by each feature. Each value is used to call the next feature, until the end of the chain; the last feature may be a query or a command.
The same result can be achieved by storing each value returned from successive calls, but temporary variables are then needed to store each returned object. A sequence of feature calls is shown below, followed by the equivalent nested feature call; both these code fragments have the same behaviour when executed.
b := a.feature1 c := b.feature2 d := c.feature3 d.feature4
a.feature1.feature2.feature3.feature4A remote call that returns the last character on the third line of the fourth page of a book, for example, could be done using the sequence of calls book.page (4).line (3).last. Remote calls should be treated very carefully, however, because a remote call often indicates a complex, hidden connection between client and supplier. Such a connection should be broken into a set of routines and the routines placed in a set of classes, so there are only simple, direct relations between two classes.
It is common to have features with the same name in different classes; this is known as overloading a name. This does not create a name clash, because Eiffel simply follows the procedure described above. As an example, we might define make routines for both a LINE and a POINT, so we have two different routines with the same name. Given the code
line: LINE ... !!line.make point: POINT ... !!point.makeEiffel finds the object from the instruction (object.feature), the class from the declaration, and then finds the correct routine in the class definition.
The name of an object is used to convey meaning about that object, and the name of the feature conveys the meaning of that feature. Consider a class ROOM that contains various heights. There is no need to call the attributes door_height, window_height, and wall_height, because they are features of the appropriate class. If an attribute is used with no client, the feature is obviously a feature of that class and can be called height. If an attribute is used externally, then the name of the object carries the meaning, such as room.height, door.height, window.height, and wall.height.
An operator is a function that is called slightly differently from a typical function. An operator provides no extra functionality in the language, because it can be implemented as a normal function. Operators provide syntactic sugar, to make the code sweeter to write. The usual mathematical notation can be used when writing expressions (such as 3 + 2) instead of the normal Eiffel operator form (which would be 3.+ (2) here).
Operators are written in two forms, called infix and prefix operators. Infix operators, such as "+", are written in the middle of their arguments, as in "3 + 5". Prefix operators, such as "not", are written before their arguments, as in "not (x > 3)". An operator is a function, so it returns a value and has a type; the returned value from "+", for example, can have the type INTEGER , REAL, or DOUBLE.
For a prefix operator, the function call lists the operator name, then the object: actual call: + me operator header: prefix "+": INTEGER is ...
For an infix operator, the function call lists the object, then the operator, and then one argument: actual call: add + me operator header: infix "+" (this: INTEGER): INTEGER is ...Three examples of feature calls are shown below, to show the different formats. Assume that we have a variable i: INTEGER, and we wish to define addition of two integers. The first example shows a function named "+", that takes a single argument, adds it to the value of the object, and returns the new value. The second example shows the infix operator binary plus that is named "+". The third example shows the prefix operator unary plus, named "+", where there is only an object and no argument:
function | object.feature (argument) | i.+ (3) |
infix operator | object feature argument | i + 3 |
prefix operator | feature object | + i |
Thprefix "+": INTEGER is infix "+" (other: INTEGER): INTEGER is prefix "-": INTEGER is infix "-" (other: INTEGER): INTEGER is infix "*" (other: INTEGER): INTEGER is infix "/" (other: INTEGER): INTEGER is