Here are some thoughts on the good parts of ‘object oriented’. It has mainly to do with abstraction and modularity which are different aspects of the same thing. By abstraction we mean localizing the code that concerns itself with how some abstract entity, AE, is represented in the computer. I call such code rep code here.
We may merely represent AE by the values of variables and refer to those variables only in one ‘module of code’ that is local in some sense. Assurance of such abstractions requires scanning all the source to verify that all references are indeed local, and also ensuring that the variables are not accessed except by their names. Scope features of languages generally solve the first problem and type safety may solve the second. The own variables of Algol 60 served thus to implement one instance of some one abstract entity.
Generally multiple instances of some class of AE are needed; often the number is known only as the application runs. It is important that the innards of each instance share just one body of rep code to deal with them. In the 60’s the structure or record was invented to satisfy the need for replication of several variables and this supported the notion of multiple entities each with their own structure. These were not abstract however. Anyone who knew the names of the fields in the struct could access them from outside the module. Common conventions assumed that declarations of such structs were public.
C++ introduced the class which was like the struct except that knowing the field names did not suffice to access them. There was a sort of ‘scope’ introduced for the class and only code in that scope could use the field names therein. Theretofore, the declaration of a struct included only variables; C++ allowed functions as well, almost as it every instance included the code. Of course the compiler was clever enough to share the code which ran with some register locating some particular instance of the collection of variables which corresponded to the conventional struct. This register got it value thru some new interpretation of syntactic constructs such as obj.meth( … ) where obj denoted an instance of an object and meth was the field name of some field therein, which was actually a function taking some arguments. The call site would include code to load the register with the address of the vestigial struct but actually branch to the rep code that was shared by the instances.
In this scheme code accessing the innards was again localized. The rep code was just that within the curly brackets of the class definition. (C++ allows discontiguous class definitions which I consider harmful. Perhaps they are warranted on practical grounds.) There was another ramification however. Code outside the rep code dealt with references to class instances, now called objects. Rep code might now find itself with pointers to two instances of objects the innards of which were know to said rep code. This would happen, for instance, if a method were called with a parameter typed as an object reference of this class. The logic of the situation suggested that the rep code could directly access the innards of the visiting sibling object. The ‘field names’ of the ‘struct’ were, after all, in scope. I think that this was the right choice but an arbitrary language design choice nonetheless. There was ready syntax at hand to support this decision. The opposite design decision might be called alienated siblings but the security advantages of that design choice are murky and perhaps nonexistent.
Access by the rep code to siblings was C++’s entré to synergy and it came at zero execution cost and near zero conceptual cost; indeed it seemed so natural that it may have gone unnoticed by even the language designers. It also supported a class of abstraction that had become familiar to many early 20th century mathematicians who taught that a complex number was merely a pair of real numbers. This let languages such as C++ support the mathematical notion just as mathematicians taught, and efficiently too.
Some languages achieve all this with type safety as well, but not C++. Simula preceded C++ and pioneered some of these ideas. I am not familiar with Simula.
C++ relates class definitions in a hierarchy and achieves some benefits of polymorphism where call sites are constructed by the compiler without complete knowledge of the type of the called object. The runtime overhead is surprisingly small. Polymorphism is good but I am not as happy with how it was achieved in C++. The polymorphisms of C++ emphasizes signature compatibility over semantic coherence. In C++ you trust whoever is in a position to extend the class that you explicitly nominate in your call. In other plans you trust only the text of that explicitly nominated class who may choose to trust other classes.
See abstraction as platform issue too.