3 Classes and objects 3.1. The nature of an object

Lecture



Both the engineer and the artist must have a good feel for the material they work with. In the object-oriented methodology of analyzing and building complex software systems, the basic building blocks are classes and objects. Above we gave only an informal definition of these two elements. In this chapter we will examine the nature of classes and objects, the relationships between them, and also offer several useful rules for designing good abstractions.

3.1. The nature of an object

What is and what is not an object?

The ability to recognize objects of the physical world is something a person possesses from the earliest age. A brightly colored ball attracts an infant's attention, but if the ball is hidden, the infant as a rule does not try to look for it: as soon as an item leaves the field of view, it ceases to exist for the infant. Only at about one year of age does a child develop the notion of an object: a skill that is indispensable for recognition. Show a ball to a one-year-old child and hide it: most likely the child will begin to search for the hidden item. The child associates the notion of an item with the permanence and individuality of its form, independently of the actions performed upon that item .

In the previous chapter an object was informally defined as a tangible entity that exhibits some well-defined behavior. From the standpoint of human perception, an object may be:

  • a tangible and/or visible item;
  • something that may be apprehended intellectually;
  • something toward which thought or action is directed.

Thus, we have extended the informal definition of an object with a new idea: an object models some part of the surrounding reality and therefore exists in time and space. The term object in software was first introduced in the Simula language and was used for modeling reality .

Objects of the real world do not exhaust the kinds of objects that are of interest in designing software systems. Other important kinds of objects are introduced at the design stage, and their interaction with one another serves as the mechanism for expressing higher-level behavior . This brings us to the more precise definition given by Smith and Tockey: "An object represents an individual, identifiable item, unit, or entity, either real or abstract, with a well-defined role in the problem domain" . In even more general terms, an object may be defined as anything with a crisply defined boundary .

Imagine a factory where composite materials are created for such different products as, say, bicycle frames and airplane wings. Factories are often divided into shops: mechanical, chemical, electrical, and so on. Shops are subdivided into areas, in each of which several pieces of equipment are installed: dies, presses, machine tools. On the production lines one can see many vessels of raw materials from which, by means of chemical processes, blocks of composite material are created. From these the final product is then made — frames or wings. Each tangible item may be regarded as an object. A lathe has crisply defined boundaries that separate it from the composite block being machined on it; a bicycle frame in turn has crisp boundaries with respect to the equipment area.

There exist objects for which explicit conceptual boundaries are defined, but the objects themselves represent intangible events or processes. For example, a chemical process at the factory may be treated as an object, since it has a crisp conceptual boundary, interacts with other objects by means of an ordered, time-distributed set of operations, and exhibits well-defined behavior. Consider a CAD/CAM solid modeling system. Two bodies, for example a sphere and a cube, as a rule have an irregular intersection. Although this line of intersection does not exist separately from the sphere and the cube, it is nevertheless an object in its own right with crisply defined conceptual boundaries.

Objects may be tangible yet have fuzzy physical boundaries: rivers, fog, or crowds of people [This is true only at a sufficiently high level of abstraction. For a person walking through a bank of fog, it is meaningless to distinguish "my fog" from "your fog". However, consider a weather map: banks of fog in San Francisco and in London are entirely different objects]. Just as the person who has picked up a hammer begins to see nothing but nails in everything around, a designer with object-oriented thinking begins to perceive the whole world in terms of objects. Of course, such a view is somewhat simplistic, since there exist notions that are clearly not objects. Among them are attributes such as time, beauty, color, emotions (for example, love or anger). However, all of the above are potentially properties inherent in objects. One may, for example, assert that a certain person (an object) loves his wife (another object), or that a particular cat (yet another object) is gray.

 3 Classes and objects  3.1. The nature of an object

An object has state, exhibits some well-defined behavior, and has a unique identity.

It is useful to understand that an object is something with crisply defined boundaries, but this is not enough to distinguish one object from another or to judge the quality of an abstraction. On the basis of the available experience, the following definition can be given:

An object has state, behavior, and identity; the structure and behavior of similar objects define their common class; the terms "instance of a class" and "object" are interchangeable.

State

Semantics. Consider a vending machine that sells beverages. The behavior of such an object is that, after a coin is dropped into it and a button is pressed, the machine dispenses the selected beverage. What happens if the beverage selection button is pressed first and only then the coin is dropped in? Most machines will simply do nothing, since the user has violated their basic rules.

In other words, the machine was playing a role (waiting for a coin) that the user ignored by pressing the button first. Or suppose the user of the machine paid no attention to the warning notice "Deposit exact change for the beverage" and dropped an extra coin into the machine. In most cases machines are not user-friendly and cheerfully swallow all the money.

In each of these situations we see that the behavior of an object is determined by its history: the sequence of actions performed upon the object matters. Such dependence of behavior on events and on time is explained by the fact that the object has an internal state. For a vending machine, for example, the state is determined by the amount of money deposited before the selection button is pressed. Other important information is the set of coins accepted and the stock of beverages.

On the basis of this example, let us give the following low-level definition:

The state of an object is characterized by the list (usually static) of all the properties of that object and the current (usually dynamic) values of each of those properties.

One of the properties of a vending machine is the ability to accept coins. This is a static (fixed) property, in the sense that it is an essential characteristic of a vending machine. On the other hand, this property has a dynamic value corresponding to it that characterizes the quantity of coins accepted. The amount increases as coins are dropped into the machine and decreases when the vendor removes the money from the machine. In some cases the values of an object's properties may be static (for example, the serial number of the machine), which is why this definition uses the term "usually dynamic".

The properties of an object include the characteristics, traits, qualities, or capabilities inherent in it or acquired by it that make the object what it is. For example, it is characteristic of an elevator that it is built to travel up and down rather than horizontally. The list of an object's properties is, as a rule, static, since these properties constitute the unchanging foundation of the object. We say "as a rule" because in a number of cases the set of an object's properties may change. An example is a robot with the capability of self-learning. The robot may initially regard some obstacle as static, and then discover that it is a door that can be opened. In such a situation, as new knowledge is acquired, the conceptual model of the world created by the robot changes.

All properties have certain values. These values may be simple quantitative characteristics, or they may refer to another object. The state of an elevator may be described by the number 3, denoting the number of the floor on which the elevator is currently located. The state of a vending machine is described in terms of other objects, for example the beverages available in stock. Specific beverages are objects in their own right, distinct from the vending machine (they can be drunk, whereas the machine cannot, and other actions can be performed with them).

Thus, we have established the distinction between objects and simple values: simple quantitative characteristics (for example, the number 3) are "constant, unchanging, and everlasting", whereas objects "exist in time, are mutable, have state, are instantiable, and can be created, destroyed, and shared" .

The fact that every object has state means that every object occupies a certain amount of space (physically or in the computer's memory).

Examples. Suppose that in C++ we need to create personnel records about employees. This can be done as follows:

struct PersonnelRecord {

char name[100];
int socialSecurityNumber;
char department[10];
float salary;

};

Each component in the structure shown denotes a specific property of our personnel record abstraction. The declaration defines not an object but a class, since it does not introduce any specific instance [More precisely, this declaration defines a structure in C++ whose semantics correspond to a class in which all fields are public. Thus, structures are unencapsulated abstractions]. In order to create objects of this class, one must write the following:

PersonnelRecord deb, dave, karen, jim, torn, denise, kaitlyn, krista, elyse;

In this case nine different objects have been declared, each of which occupies a certain area in memory. Although the properties of these objects are common (their state is represented uniformly), in memory the objects do not overlap and each occupies its own place. In practice it is customary to restrict access to an object's state rather than make it publicly available, as in the previous class definition. In light of what has been said, let us change this definition as follows:

class PersonnelRecord {
public:

char* employeeName() const;
int employeeSocialSecurityNumber() const;
char* employeeDepartment() const;

protected:

char name[100];
int socialSecurityNumber;
char department[10];
float salary;

};

The new definition is somewhat more complicated than the previous one, but for a number of reasons it is preferable [On the question of style: by the criteria introduced later in this chapter, the proposed definition of the class PersonnelRecord is far from a masterpiece. Here we only want to show the semantics of a class's state. Having in a class a function that returns a char* value is often dangerous, since it violates the memory-protection paradigm: if a method allocates memory for itself for which the client that gained access to it is not responsible, the result will be memory garbage. In our systems we prefer to use a parameterized class of variable-length strings, which can be found in a foundation class library such as the one described in Chapter 9. And further: classes are more than C structures with C++ class syntax; as explained in Chapter 4, classification requires a certain harmony of structure and behavior]. In particular, in the new definition the implementation of the class is hidden from other objects. If the implementation of the class is later changed, the code will have to be recompiled, but semantically clients will not depend on these changes (that is, their code will be preserved). In addition, the problem of the memory occupied by an object is also solved, through the explicit definition of the operations that clients are permitted to perform on objects of this class. In particular, we give all clients the right to find out an employee's name, social security number, and place of work, but only special clients (namely, subclasses of this class) are permitted to set the values of these parameters. Only these special clients are permitted access to salary information. Another merit of the latter definition is connected with the possibility of its reuse. In the next section we will see that the inheritance mechanism makes it possible to reuse an abstraction and then to refine and specialize it in many ways.

In conclusion, let us say that all objects in a system encapsulate some state, and all of the system's state is encapsulated in objects. However, encapsulating an object's state is only a beginning, which is not enough for us to capture the full meaning of the abstractions we introduce during development. For this reason we need to understand how objects function.

Behavior

What behavior is. Objects do not exist in isolation, but are acted upon or themselves act upon other objects.

Behavior is how an object acts and reacts; behavior is expressed in terms of the object's state and message passing.

In other words, an object's behavior is its externally observable and testable activity.

An operation is a particular action performed by one object upon another in order to elicit a corresponding reaction. For example, a client can invoke the operations append and pop in order to manipulate a queue object (to add or remove an element). There is also the operation length, which makes it possible to determine the size of the queue but cannot change this value. In a purely object-oriented language such as Smalltalk, it is customary to speak of message passing between objects. In languages of the C++ type, in which the procedural past is more clearly felt, we say that one object invokes a member function of another. Essentially the notion of a message coincides with the notion of an operation on objects, although the delivery mechanism is different. For our purposes these two terms may be used as synonyms.

In object-oriented languages the operations performed upon a given object are called methods and form part of the definition of the object's class. In C++ they are called member functions. We will use these terms as synonyms.

Message passing is one part of the equation that defines behavior. It follows from our definition that an object's state also affects its behavior. Consider a vending machine. We can make a selection, but the machine's behavior will depend on its state. If we have not deposited a sufficient sum into it, most likely nothing will happen. But if the money is sufficient, the machine will dispense what we want (and thereby change its state). Thus, an object's behavior is determined by the operations performed upon it and by its state, and some operations have a side effect: they change the state. The concept of a side effect allows us to refine our definition of state:

The state of an object represents the cumulative result of its behavior.

The most interesting objects are those whose state is not static: their state is changed and queried by operations.

Examples. Let us describe in C++ the class Queue:

class Queue {
public:

Queue();
Queue(const Queue&);
virtual ~Queue();
virtual Queue& operator=(const Queue&);
virtual int operator==(const Queue&) const;
int operator!=(const Queue&) const;
virtual void clear();
virtual void append(const void*);
virtual void pop();
virtual void remove(int at);
virtual int length() const;
virtual int isEmpty() const;
virtual const void* front() const;
virtual int location(const void*);

protected:
...
};

The class definition uses the usual C idiom of referring to data of unspecified type by means of void*, thanks to which objects of different classes can be inserted into the queue. This technique is not safe — the client must clearly understand which object (of which class) it is dealing with. In addition, when void* is used the queue does not "own" the objects placed in it. The destructor ~Queue() destroys the queue but not its members. In the next section we will consider parameterized types, which help to cope with such problems.

Since the definition of Queue specifies a class and not an object, we must declare instances of the class with which clients can work:

Queue a, b, c, d;

We can perform operations on the objects:

a.append(&deb);
a.append(&karen);
a.append (&denise);
b = a;
a.pop();

Now the queue a contains two employees (karen is first), and the queue b contains three (deb is first). Thus, the queues have a certain state that affects their future behavior — for example, one queue can safely be advanced (pop) two more times, and the other three.

Operations. An operation is a service that a class can provide to its clients. In practice a typical client performs operations of five kinds on objects [Lippman proposed a somewhat different classification: manager functions, implementor functions, helping functions (all kinds of modifiers), and access functions (equivalent to selectors) ]. Below are the three most common operations:

® Modifier An operation that alters the state of an object
® Selector An operation that reads the state of an object, but does not alter the state
® Iterator An operation that permits all parts of an object to be accessed in some strictly defined order


Since the logic of these operations is quite different, it is useful to choose a programming style that reflects these differences in the program code. In our specification of the class Queue we first listed all the modifiers (member functions without the const specifier — clear, append, pop, remove), and then all the selectors (functions with the const specifier — length, isEmpty, front, and location). Later, in Chapter 9, following our style, we will define a separate class that acts as an agent responsible for iterating over queues.

Two operations are universal; they provide the infrastructure needed to create and destroy instances of a class:

® Constructor An operation that creates an object and/or initializes it
® Destructor An operation that frees the state of an object and/or destroys the object itself


In C++ the constructor and the destructor form part of the class declaration, whereas in Smalltalk and CLOS these operations are defined in the protocol of the metaclass (that is, the class of the class).

In purely object-oriented languages such as Smalltalk, operations can only be methods, since defining procedures and functions outside classes is not permitted in this language. By contrast, in the languages Object Pascal, C++, CLOS, and Ada it is permitted to describe operations as subprograms independent of objects. In C++ they are called nonmember functions; here we will call them free subprograms. Free subprograms are procedures and functions that play the role of high-level operations upon an object or objects of one or of different classes. Free procedures are grouped according to the classes for which they are created. This gives grounds for calling such packages of procedures class utilities. For example, for the class Queue defined above, one can write the following free procedure:

void copyUntilFound(Queue& from, Queue& to, void* item)
{

while ((!from.isEmpty()) && (from.front() != item))
{

to.append(from.front());
from.pop();

}}

The point is that the contents of one queue are transferred to another until the given object appears at the head of the first queue. This is a high-level operation; it is built upon the primitive operations of the class Queue.

In C++ (and Smalltalk) it is customary to collect all logically related free subprograms and declare them part of some class that has no state. All such functions will be static.

Thus, it may be asserted that all methods are operations, but not all operations are methods: some of them are free subprograms. We are inclined to use only methods, although, as will be shown in the next section, it is sometimes hard to resist the temptation, especially if by its nature an operation is performed upon several objects of different classes and there is no reason to declare it an operation of one particular class rather than another.

Roles and responsibilities. The totality of all methods and free procedures relating to a specific object forms that object's protocol. The protocol thus defines the behavior of the object, encompassing all its static and dynamic aspects. In the most nontrivial abstractions it is useful to subdivide the protocol into particular aspects of behavior, which we will call roles. Adams says that a role is a mask that an object wears ; it defines the abstraction's contract with its clients.

Combining our definitions of an object's state and behavior, Wirfs-Brock introduces the notion of responsibility. "The responsibilities of an object have two sides — the knowledge that the object maintains, and the actions that the object can perform. They express the meaning of its purpose and its place in the system. Responsibility is understood as the totality of all the services and all the contractual obligations of an object" . Thus it may be said that the state and behavior of an object define the roles it plays, and those in turn are needed to carry out the responsibilities of the given abstraction.

Indeed, most interesting objects play different roles in their lifetime, for example [10]:

  • A bank account may be in good or bad standing (two roles), and what happens when an attempt is made to withdraw money from it depends on this role.
  • For a stock broker a block of shares is a commodity that can be bought or sold, while for a lawyer it is a token of ownership of certain rights.
  • In the course of a day one and the same person may play the role of mother, physician, gardener, and film critic.

The roles of a bank account are dynamic and mutually exclusive. The roles of a block of shares overlap slightly, but each of them depends on what the client does with them. In the case of a person the roles change dynamically every minute.

As we will see in Chapters 4 and 6, we often begin our analysis by enumerating the different roles an object can play. During design we single out these roles, introducing specific operations that carry out the responsibilities of each role.

Objects as finite state machines. The presence of an internal state in objects means that the order in which operations are performed is of essential significance. This suggests representing an object as a small independent machine [11]. Indeed, for a number of objects this temporal ordering is so essential that the best way of describing them formally is a finite state machine. In Chapter 5 we will introduce a notation for describing hierarchical finite state machines, which can be used to express the corresponding semantics.

Continuing the analogy with machines, one may say that objects can be active and passive. An active object has its own thread of control, and a passive one does not. An active object is in the general case autonomous, that is, it can exhibit its behavior without being acted upon by other objects. A passive object, by contrast, can change its state only under the action of other objects. Thus, the active objects of a system are the sources of controlling actions. If a system has several threads of control, then there may also be several active objects. In sequential systems there is usually only one active object at any moment in time, for example the main window, whose dispatcher catches and processes all messages. In such a case the remaining objects are passive: their behavior is exhibited when the active object turns to them. In other kinds of sequential architectures (transaction processing systems) there is no explicit center of activity, and control is distributed among the passive objects of the system.

Identity

Semantics. Khoshafian and Copeland proposed the following definition:

"Identity is that property of an object which distinguishes it from all other objects" [12].

They note that "in most programming languages and database management systems, temporary objects are named in order to distinguish them, thereby confusing addressability with identity. Most databases distinguish persistent objects by a key attribute, thereby confusing identity with data value". A source of many errors in object-oriented programming is the inability to distinguish the name of an object from the object itself.

Examples. Let us begin by defining a point on a plane.

struct Point {

int x;
int y;
Point() : x(0), y(0) {}
Point(int xValue, int yValue) : x(xValue), y(yValue) {)

};

We defined point as a structure rather than as a full-fledged class. The rule on the basis of which we did so is very simple. If an abstraction is a collection of other objects without any behavior of its own, we make it a structure. However, when our abstraction implies more complex behavior than simple access to the fields of a structure, then a class must be defined. In this case the abstraction point is simply a pair of coordinates (x, y). For convenience two constructors are provided: one initializes the point with zero coordinate values, and the other with certain given values.

Now let us define a display item (DisplayItem). This abstraction is fairly common in systems with a graphical user interface (GUI) — it is the base class for all objects that can be displayed in a window. We want to make it something more than merely a collection of points. Clients should be able to draw items, select them, and move them about the screen, as well as query their position and state. We write our abstraction in the form of the following declaration in C++:

class DisplayItem {
public:

DisplayItem();
DisplayItem(const Point& location);
virtual ~DisplayItem();
virtual void draw();
virtual void erase();
virtual void select();
virtual void unselect();
virtual void move(const Point& location);
int isSelected() const;
Point location() const;
int isUnder(const Point& location) const;

protected:
...
};

In this declaration we have deliberately omitted the constructors, as well as the operators for copying, assignment, and testing for equality. We will leave them until the next section.

We expect that this class will have many descendants, and therefore the destructor and all the modifiers are declared virtual. This applies especially to draw. By contrast, the selectors will most likely not be redefined in subclasses. Note that one of them, isUnder, must compute whether the object covers the given point, rather than simply return the value of some property.

Let us declare instances of the classes indicated:

DisplayItem item1;
DisplayItem* item2 = new DisplayItem(Point(75, 75));
DisplayItem* item3 = new DisplayItem(Point(100, 100));
DisplayItem* item4 = 0;

Figure 3-1a shows that executing these statements gives rise to four names and three different objects. Specifically, four locations in memory will be allocated for the names item1, item2, item3, item4. Here item1 will be the name of an object of the class DisplayItem, and the other three will be pointers. Moreover, only item2 and item3 will actually point to objects of the class DisplayItem. The objects pointed to by item2 and item3 have, in addition, no names, although they can be referred to by "dereferencing" the corresponding pointers: for example, *item2. We can therefore say that item2 points to a separate object of the class DisplayItem, whose name we can refer to indirectly through *item2. The unique identity (but not necessarily the name) of each object is preserved for the whole time of its existence, even if its internal state has changed. This situation is reminiscent of Zeno's paradox of the river: can a river be the same one if different water flows in it every day?

 3 Classes and objects  3.1. The nature of an object

Figure 3-1. The identity of objects.

Consider the result of executing the following statements (Figure 3-1b):

item1.move(item2->location());
item4 = item3;
item4->move(Point(38, 100));

The object item1 and the object pointed to by item2 now refer to one and the same point on the screen. The pointer item4 has come to point to the same object as item3. Incidentally, note the difference between the expressions "the object item2" and "the object pointed to by item2". The second expression is more precise, although for brevity we will often use them as synonyms.

Although the object item1 and the object pointed to by item2 have identical state, they remain different objects. In addition, we changed the state of the object *item3 by using its new indirect name item4. This is the situation we call structural dependency, meaning by that a situation in which an object is named in more than one way by several synonymous names. Structural dependency gives rise to many problems in object-oriented programming. The difficulty of recognizing side effects when acting upon synonymous objects often leads to "memory leaks", improper memory access, and, worse still, unpredictable changes of state. For example, if we destroy the object through the pointer item3, then the value of the pointer item4 will turn out to be meaningless; this situation is called a dangling reference. Figure 3-1c illustrates the result of performing the following actions:

item2 = &item1;
item4->move(item2->location());

The first line creates a synonym: item2 points to the same object as item1. In the second, access to the state of item1 is obtained through this new synonym. Unfortunately, a memory leak has occurred in the process — the object originally pointed to by the reference item2 is not named in any way, either directly or indirectly, and its identity is lost. In Smalltalk and CLOS the memory allocated for objects will be returned to the system again by the garbage collector. In languages of the C++ type such memory is not freed until the program that created the object terminates. Such memory leaks can cause both mere inconvenience and major failures, especially if the program must run continuously for a long time [Imagine a memory leak in a program controlling a satellite or a cardiac pacemaker. Restarting the computer on a satellite several million kilometers from Earth is very inconvenient. Similarly, unpredictable garbage collection in a program controlling a pacemaker could prove fatal for the patient. In such cases developers of real-time systems prefer to refrain from dynamic memory allocation].

Copying, assignment, and equality. Structural dependency occurs when an object has several names. In the most interesting applications of the object-oriented approach the use of synonyms is simply unavoidable. For example, consider the following two functions:

void highLight(DisplayItem& i);
void drag(DisplayItem i); // Dangerous

If the first function is called with the parameter item1, an alias will be created: the formal parameter i denotes a pointer to the actual parameter, and consequently item1 and i name one and the same object during the execution of the function. When the second function is called with the argument item1, a new object is passed to it, being a copy of item1: i denotes an entirely different object, though one with the same state as item1. C++ distinguishes between passing parameters by reference and by value. One must watch out for this, otherwise one may inadvertently change a copy of an object while intending to change the object itself [In Smalltalk the semantics of passing objects to methods as arguments is in spirit the equivalent of passing a parameter by reference in C++]. As we will see in the next section, passing objects by reference in C++ is necessary for programming polymorphic behavior. In the general case, passing objects by reference is highly desirable for sufficiently complex objects, since in that case the reference is copied rather than the state, and consequently greater efficiency is achieved (except in those cases where the value being passed is very simple).

In some circumstances, however, copying is precisely what is meant. In languages of the C++ type the semantics of copying can be controlled. In particular, we can introduce a copy constructor into the definition of a class, as in the following fragment of code, which could be included in the declaration of the class DisplayItem:

DisplayItem(const DisplayItem&);

In C++ the copy constructor may be invoked explicitly (as part of the declaration of an object) or implicitly (when an object is passed by value). The absence of this special constructor causes the default copy constructor to be invoked, which copies the object memberwise. However, when an object contains references or pointers to other objects, such an operation leads to the creation of synonymous pointers to objects, which makes memberwise copying dangerous. We propose an empirical rule: permit implicit replication by copying only for objects containing exclusively primitive values, and make it explicit for more complex objects.

This rule clarifies what some languages call "shallow" and "deep" copying. To copy an object, Smalltalk introduces the methods shallowCopy (the method copies only the object, and the state is shared) and deepCopy (the method copies the object and the state, recursively if needed). By redefining these methods for classes with aggregation, one can achieve the effect of "deep" copying for some parts of an object and "shallow" copying for the remaining parts.

Assignment is, generally speaking, copying. In C++ its meaning can also be changed. For example, we could add to the definition of the class DisplayItem the following line:

virtual DisplayItem& operator=(const DisplayItem&);

This operator has deliberately been made virtual, since it is expected that subclasses will redefine it. As in the case of the copy constructor, copying can be made "deep" or "shallow". If the assignment operator is not explicitly redefined, then by default the object is copied memberwise.

Closely connected with the question of assignment is the question of equality. Although the question seems simple, equality can be understood in two ways. First, two names may denote one and the same object. Second, it may be the equality of state of two different objects. In the example in Figure 3-1c both variants of sameness will hold for item1 and item2. However, for item2 and item3 only the second variant will be true.

C++ has no predefined equality operator, and therefore we must define equality and inequality by declaring these operators in the declaration:

virtual int operator=(const DisplayItem&) const;
int operator!=(const DisplayItem&) const;

We propose declaring the equality operator as virtual (since we expect that subclasses may redefine its behavior), and declaring the inequality operator as nonvirtual (since we want it always to be the logical negation of equality: subclasses should not redefine this).

In a similar way we can create comparison operators for objects such as >= and <=.

The lifetime of objects. The beginning of the lifetime of any object is the moment of its creation (the allocation of a region of memory), and the end is the return of the allocated region of memory to the system.

Objects are created explicitly or implicitly. There are two ways to create them explicitly. First, this can be done at declaration (as was the case with item1): then the object is placed on the stack. Second, as in the case of item3, an object can be allocated, that is, given memory from the "heap". In C++ in either case a constructor is invoked, which allocates the amount of properly initialized memory known to it for the object. In Smalltalk this is handled by metaclasses, whose semantics we will discuss later.

Objects are often created implicitly. Thus, passing a parameter by value in C++ creates a temporary copy of the object on the stack. Moreover, the creation of objects is transitive: creating an object entails the creation of the other objects that are part of it. Redefining the semantics of the copy constructor and the assignment operator in C++ permits explicit control over when the parts of an object are created and destroyed. In addition, in C++ one can also redefine the operator new, thereby changing the policy of managing memory in the "heap" for individual classes.

In Smalltalk and some other languages, when the last reference to an object is lost, it is reclaimed by the garbage collector. In languages without garbage collection, such as C++, objects created on the stack are destroyed on exit from the block in which they were defined, but objects created in the "heap" by the operator new continue to exist and occupy space in memory: they must be explicitly destroyed by the operator delete. If an object is "forgotten" and not destroyed, this will cause, as was already said above, a memory leak. But if an attempt is made to destroy the object a second time (for example, through another pointer), the consequence will be a memory violation message or a complete crash of the system.

When an object is destroyed explicitly or implicitly in C++, the corresponding destructor is invoked. Its task is not only to free memory, but also to decide what to do with other resources, for example with open files [Destructors do not automatically free memory allocated by the operator new; programmers must free it explicitly].

The destruction of long-lived objects has somewhat different semantics. As was said in the previous chapter, some objects may be long-lived; by this it is meant that their lifetime may extend beyond the lifetime of the programs that gave rise to them. Usually such objects are part of some long-term object structure, and therefore the questions of their life and death relate rather to the policy of the corresponding object-oriented database. In such systems, to ensure long life the most widely accepted approach is based on persistent "mixin classes". All objects to which we want to give long life must inherit from these classes.

created: 2020-12-19
updated: 2026-03-10
206



Was this answer useful?
Choose a quick rating so we can improve the next answer for you.
How satisfied are you?


Comments

To leave a comment

If you have any suggestion, idea, thanks or comment, feel free to write. We really value feedback and are glad to hear your opinion.
To reply

Lectures and tutorial on "Object Oriented Analysis and Design"

Terms: Object Oriented Analysis and Design