I Blame the Parents
The big contribution made by Simula was the idea of a virtual function. Combined with inheritance, virtual functions extended ALGOL's concept of records to allow subtype polymorphism.
In ALGOL, when you defined a record and a function manipulating that record, the behavior was fixed. You could sometimes create a new structure that started the same way as the original but then had some extra fields, so the code expecting the original structure saw something with the same layout, but you couldn't alter the behavior of functions. You could define new functions, but other code had to be modified to use them.
With Simula, you could create subclasses. A trivial subclass had the same layout and behavior as its parent. More commonly, it would extend one or both. In addition to the extensions possible with ALGOL-style records, it could override member functions that were declared as virtual. Therefore, you could either augment or completely replace code.
When you called a normal ALGOL-style function, the compiler translated the call into a jump to a fixed address. When you called a virtual function, it loaded that address from a fixed offset from a table. Simula classes were effectively ALGOL records with a pointer to a table containing the addresses of the virtual functions in the class. This design made calls to virtual functions slightly slower, but added a lot of flexibility because each class could implement a new version of the function.
Virtual functions were very important for Simula's goal of writing simulations. Typically, a simulation included a lot of specialized forms of generic things. For example, a simulation of a structure might have a set of different materials that behaved slightly differently under different stresses. Each of these materials would be a subclass of some generic materials class, but their behavior would be implemented in virtual functions.
Virtual functions were useful in a lot of other contexts—especially in shared libraries, because they provided a clean way for old code to call new code, as a special case of function pointers. This kind of thing is useful in a lot of cases. Consider the C standard library function qsort(), which sorts an array using a compare function passed in as an argument. Having qsort() available avoids the need for every developer to implement a sort function, but it's only possible because C has function pointers.
With virtual functions, each object can provide its own compare statement, giving another way of implementing this objective. Sorting is a very simple example. Quite often, you need to perform several operations on data that the user provides to the library. A fairly common example is a generic implementation of a map, which typically requires functions that do the following:
- Generate a hash from a key
- Compare two keys
- Copy the key into the map
- Copy the value into the map
- Destroy a key
- Destroy a value
So you need to provide at least six functions when creating the map. If the map needs to support more than one type of object as a key or a value, these functions are very complicated. With Simula, each object would implement hash, compare, copy, and destroy virtual functions, and the map could be completely generic, just calling these functions.
This approach simplifies code, but it also helps with encapsulation and maintainability. The comparison function is defined by the objects, which has several beneficial side effects: It simplifies understanding the object, because all of the code describing how it works is in the same place. It also decreases the likelihood that two developers will implement the same function.