Specifying the Class as Abstract
Specifying the Class as Sealed
CLR Inheritance: Specifying the Base Class
Declaration of a CLR Reference Class Object
Dynamic Allocation of an Object on the CLR Heap
A Tracking Reference to No Object
In Managed Extensions for C++, Destructors go to Finalize()
In New Syntax, Destructors go to Dispose()
Declaring a Reference Object
Declaring an Explicit Finalize() (!R)
What This Means Going from Managed Extensions for C++ to Visual C++ 2005
Object-oriented programming has the advantage of being a natural paradigm in which to develop systems. We perceive the world as consisting of objects: tables, chairs, computers, cars, bank accounts, rugby matches, and overdrafts. It is also a natural human trait to try to organize these objects, arranging them into some form of classification, choosing to highlight certain features of objects in preference to others. So, dogs and cats are mammals, toasters and refrigerators are appliances, rugby and tennis are sports, Jaguars and Fords are cars, trucks and cars are vehicles, and so on. There can be many levels to these categories and many ways to classify the objects in the world. How people classify things depends to a large extent on what they want to do with them and the features that are salient to these desires. For example, an electrical engineer is likely to have different, and in some ways deeper and richer, categories for household appliances than a teacher might have. While grouping objects into classification schemes, we also tend to highlight certain attributes of objects in preference to others. For example, a car’s color might not matter in an engineer’s mind, but it might figure heavily in a Ferrari salesperson’s mental model of car classifications.
The idea of building hierarchies of related objects is used in object-oriented programming. As long ago as the 1960s, researchers realized that many computer program model entities can be named and their properties and behavior can be described. They noticed that programs deal with data such as bank accounts, arrays, files, and users, which are analogous to objects in the real world. Object-oriented programming can crudely be characterized as identifying the relevant objects, organizing them into hierarchies, adding the attributes to the objects that describe the relevant features in the problem context, and adding the functions (methods) to the objects to perform the required tasks on the object. The details are a little more complicated, but essentially, it is a simple and natural process. Yet simple and natural doesn’t necessarily mean easy. A collection of objects could be classified in virtually countless ways. The ability to identify the important attributes of objects and to form good abstractions and appropriate hierarchies is key. Even within the context of a problem domain, it’s sometimes hard to determine the correct levels of abstraction and suitable classification hierarchies. Just deciding which class or grouping an object belongs to can be very difficult. As Wittgenstein (1953) pointed out, some objects will bear more of a family resemblance to a concept than others; for example, rugby and tennis are more obviously sports than are chess and synchronized swimming.
Features of Object-Oriented Programming Languages
It is already said that object-oriented programming means defining and building hierarchies of objects and defining their properties and behavior. You can do this to a certain extent in any programming language, just the same as you could, theoretically, take a trip across the Rockies in a golf cart, but it is much easier to do object-oriented programming if you use a language that is designed to support object-oriented programming methods. Object-oriented programming languages, such as C++ and C#, are characterized by three key features: encapsulation, inheritance and polymorphism that support this natural process of identifying and classifying objects. Though those features are not new, we will go through it again.
One of the problems faced by software developers is that the systems we are developing are getting increasingly larger and increasingly more complex. Encapsulation helps by breaking a program down into small, self-contained entities. For example, if you’re building an accounting system, you’ll probably need objects to represent accounts and invoices. Once you’ve developed the Account class, you no longer need to worry about the details of the implementation of the class. You can use the class anywhere in your program in much the same way you would use a built-in type, such as an integer. The class will expose the essential features of the Account object while hiding the implementation details.
The account’s name and the state of its balance are some of the attributes of the object that the client is interested in and needs to know. Details of how the account name is stored, whether it’s an array of 50 characters or a string object, or the fact that the account’s balance is maintained as a currency variable, are irrelevant to the client. The process of hiding the data structures and implementation details of an object from other objects in the system is called data hiding, and it prevents the other objects from accessing details they don’t need to know about. Encapsulation makes large programs easier to comprehend; data hiding makes them more robust. Objects can interact with other objects through only the publicly exposed attributes and methods of the object. The more attributes and methods publicly exposed, the more difficult it will be to modify the class without affecting the code that uses the class. A hidden variable could be changed from a long to a double without affecting the code that uses objects created (instantiated) from that class. The programmer would have to worry only about the methods in the class that accessed that variable, rather than worry about all the places in the program that an object instantiated from that class might be called.
The natural tendency for humans to classify objects into hierarchies is useful from a programmer’s perspective and is supported in all true object-oriented languages, including C++, by inheritance. Inheritance provides two advantages to the C++ programmer. First, and most important, it lets them build hierarchies that express the relationships between types. Suppose that you have two classes, SavingsAccount and CheckingAccount, both of which are derived from the parent Account class. If you have a function that requires an Account as an argument, you can pass it a SavingsAccount or a CheckingAccount because both classes are types of Account. Account is a general classification, and CheckingAccount and SavingsAccount are more specific types. The second advantage of object-oriented programming is that classes can inherit features from classes higher in the hierarchy. Instead of developing new classes from scratch, new classes can inherit the functionality of existing classes and then modify or extend this functionality. The parent class from which the new class inherits is known as the base class, and the new class is known as the derived class. One of the major tasks facing developers is finding appropriate classifications for the objects and classes for their programs. For example, if you need to develop classes for a driving game, it makes more sense for you to develop a general car class and then use this class as a base class for specific car types such as Jaguar or Ford. These derived classes would then extend or modify the general car class by adding new attributes and methods or by overriding existing methods. Decomposing objects into sub-objects for example, a car consists of an engine and a chassis, simplifies the development effort. As a result, each of the objects is simpler and therefore easier to design and implement than the collective whole.
The third feature of object-oriented programming languages is polymorphism, which is Greek for “many forms.” It is quite a hard concept to define, so we’ll use some examples to show you what polymorphism is and leave the precise definitions to more academic writers. Polymorphism essentially means that classes can have the same behavior but implement it in different ways. Consider several different types of vehicle: they all need to be started, so in programming terms, we could say that all vehicles have “start” functionality. Exactly how starting is implemented depends on the vehicle: if it is a Model T Ford, starting will mean cranking the starting handle, but if it is a modern car, starting will mean turning the key in the ignition. If the vehicle is a steam locomotive, starting will be a very different and more complex process.
As another example, consider the SavingsAccount and CheckingAccount types we mentioned earlier. All types derived from Account share certain functionality, such as the ability to deposit, withdraw, and query the balance. They might implement them in different ways because CheckingAccount might permit an overdraft while SavingsAccount might give interest, but they all work the same way. This means that if we passed an Account, it doesn’t matter exactly what type of account it is; we can still deposit funds, withdraw funds, and query the balance. This functionality is useful in programming terms because it gives you the ability to work with generic object types, accounts and vehicles, when you’re not concerned with the way in which each class implements functionality.
Classes and Objects
Up to this point in the chapter, the terms class and object have been used fairly interchangeably. However, classes and objects aren’t the same thing. As the name implies, object-oriented programming is about objects. An object is composed of data that describes the object and the operations that can be performed on the object. However, when you create a program in C++, you declare and define classes, not objects. A class is a user-defined type; it encapsulates both the data and the methods that work on that data. With the exception of static functions, you cannot use classes directly. A class is much more like a template, which is used to create (instantiate) objects. Just as you have to declare an int variable before you can use it, you also have to instantiate an object of the class before it can be used.
For example, you would not declare and define an Animal object. Instead, you would declare and define an Animal class and its attributes and methods. The class represents the concept, so the Animal class does not represent a specific animal but the class of all animals. When you want to use an Animal object, you have to instantiate an Animal object from the class. The class can be considered as the abstract representation of an entity, while the instantiation of the class, the object, is the concrete representation.
Benefits to the Developmental Life Cycle
There are three key benefits to object-oriented programming: comprehensibility, re-usability, and extensibility. Breaking code down into classes makes it more comprehensible by imposing a structure as programs get larger and larger. The ideal is to assemble object-oriented systems from pre-written classes and to make the required modifications to support the new requirements by using inheritance to derive new classes from the existing classes. The existing classes are reused as building blocks and not altered in any way. Creating systems from reusable components naturally leads to higher productivity, which is probably the most frequently cited benefit of object-oriented approaches. Object-oriented programming should also result in higher-quality systems. Classes that are being reused, having been tested and developed on earlier projects, are likely to contain fewer bugs than classes developed from scratch. Over the passage of time, bugs have been found and fixed in these classes, whereas a class that is written from scratch has yet to pass through the same bug detection and fixing process.
The features (encapsulation, inheritance, and polymorphism) of object-oriented programming also provide benefits. Encapsulation makes it easier to scale up from small systems to large systems. To a large extent, regardless of the size of the system, the developer is simply creating objects. Large systems might require more objects than small systems, but the level of complexity facing the developer is not significantly increased. Inheritance helps to improve the flexibility and extensibility of systems, hence reducing their costs to maintain. Deriving new classes from existing classes provides additional functionality and allows the extension of the software without altering the existing classes. Finally, data hiding also leads to more secure systems. The state of an object can be modified only by its publicly exposed methods, which increases the predictability of object behavior.