Session 1 Summary

Code discussed in this session

C++ Programming Course, Summer Term 2018, 13. April 2018

“I am sitting in a garden with a philosopher. Pointing to a nearby tree he says: ‘I know that THIS is a tree’! To another man passing by, overhearing this statement, I explain: ‘Oh no, he is not insane, we are just philosophing’”.
– Wittgenstein. Über Gewißheit. 1951.

This course is not about the C++ programming language

Semi-formal Definitions

Object (general)
location in memory with a value of a specific type, (usually) referenced by a named identifier
Type
a classification of data used to communicate the intention of its usage towards the compiler
Class (mathematical)
A collection that is defined based on common properties of its elements.
Class (technical)
specifies a type of objects with their data members and operations (methods) that transfer these objects from one valid state to another
Instance / Class Object / Object (OOCP)
in this course, we use the term instance for an object if its type is a class (structured), class object is a synonym that emphasizes the class-based mechanisms of an instance

Q: What is the purpose of formal definitions? How are mathematical definitions and the skill to interpret them related to software engineering?

Object Oriented Programming: Simplified Models

Functional Style: State is passed between functions

\[ method: Type \rightarrow \dots parameters \dots \rightarrow Type \]

Which is equivalent to a C signature like:

Type * method(const Type * obj, ... parameters ...);
// state of obj is not changed by operation, mofidied state is
// returned as new object

For example:

Collection * collection__add_elem(const Collection * c, const Element * e);

Collection * c = collection__add(
                   collection_add(collection__new(), 123),
                   456);

Imperative Style: State is persisted in objects

void method(Type * obj, ... parameters ...);
// state of obj is altered after operation

For example:

void collection__add_elem(Collection * c, const Element * e);

Collection * c = collection__new();
collection__add_elem(c, 123);
collection__add_elem(c, 456);

Both functional and imperative style illustrated above are object-oriented, they just differ in the representation of “state”.

Likewise, both notations are procedural as the operations in either variant take effect in the order they have been stated.

Object-Oriented C

Members and Methods

In C, we can use structs to represent a type as a sequential arrangement of data in memory:

typedef struct {
  unsigned char r;
  unsigned char g;
  unsigned char b;
} RGB;

typedef struct {
  unsigned int x;
  unsigned int y;
  RGB;
} Pixel;

typedef struct {
  Pixel line_points[100];
} DrawLine;

Q: This shows has-a relationships (containment) in the OO sense.
How could we express is-a dependencies?

The object-oriented paradigm implies that types may be specified to follow the Liskov substitution principle: specialized types must be semantically able to act as any of their base types.

More precisely, how could we achieve this to work:

Motorboat * m = motorboat__new();
Astrovan  * a = astrovan__new();

motorboat_max_passengers = vehicle__max_passengers(m);
astrovan_max_passengers  = vehicle__max_passengers(a);

In format terms, a substitution (\(b \implies B) / (s \implies S:B\)) of an instance \(b\) with supertype \(B\) and subtype \(S\) is subject to the constraints:

  • Preconditions of \(B\) cannot be strengthened in a subtype
  • Postconditions of \(B\) cannot be weakened in a subtype
  • Invariants of \(B\) are preserved in \(S\)

However, you will soon be convinced that inheritance is far less important for sound concept definitions than you might have been told so far (see Composition over Inheritance).

States and Side-effects

The notion of side-effects is common in the functional programming mind set. In the conventional mathematical interpretation of the function concept, a function call is a static mapping between value spaces. As a consequence in functional programming, any function call could be replaced by its result. Functions that satisfy this property named referential transparency are called pure.

Any persistent, observable effect of a function call would violate referential transparency. For example, calls of printf could not be replaced by their returned result (usually 0) as this would eliminate the intended side-effect on the console.

Unfortunately, every operation on a physical computational machine does have side-effects. At the very least, the CPU instruction counter is changed. Functional programming languages like Haskell therefore classify some side-effects as negligible.

Typically, a functional programming runtime environment aims at hiding side-effects from the programmer by means of mechanisms like garbage collection. Allocating data on the heap is definitely a side-effect, but considered irrelevant to functional semantics.

typedef struct {
  size_t   _count;
  double * _values;
} Vector;

Vector * vector__new    ();

void     vector__push   (Vector * _this, double value);
double   vector__pop    (Vector * _this);
double   vector__at     (Vector * _this, int index);
unsigned vector__size   (Vector * _this);
double   vector__sum    (Vector * _this);

Now consider:

double dummy() {
  Vector * vectors[10];
  for (int i = 0; i < 10; i++) {
    Vector * v = vector__new();
    vector__push_back(v, i * 123);
    vectors[i] = v;
  }
  // ...
  return 123;
}

What’s the problem with this function?

Value Semantics and the (classical) Rule of Three

Class objects that maintain ownership of a resource need special definitions in their behaviour. The crucial aspects are:

  • Taking ownership
  • Sharing ownership
  • Releasing ownership
  • Transferring ownership

… of a resource.

Transferring ownership consists of coordinated sharing and releasing ownership. But especially in multi-threaded situations it is more complex than doing one after the other.

In our Vector example, we need to define the additional methods:

void     vector__copy   (Vector * other);
void     vector__delete (Vector * _this);
Vector * vector__assign (Vector * _this, Vector * other);

And indeed, there is a fundamental law (rules, rather) behind this:

If the implementation of a type requires to specify one of the operations for copying, assignment or destruction, all three of those must be specified.

This is called the Rule of Three (later: Rule of Five and Rule of Zero).

See: http://en.cppreference.com/w/cpp/language/rule_of_three

Standard Object Semantics

Allocation
reserves memory for an object
Initialization
writes initial values to instance members in previously allocated memory
Construction, Instantiation
allocation and initialization; construction is a public operation, allocation and initialization should not be directly accessible as separate operations
Copy
special case of construction / instantiation where instance members are initialized with another object’s state instead of default values
Assignment
operation performed at a left-hand side (lhs) object such that its state is updated to a right-hand side (rhs) object; after assignment, equality comparison of lhs and rhs must return true.
Destruction
Free instance members and release resources

Developing Conceptual Perfect Pitch

In the “perfect pitch training” sections I am playing the devil’s advocate in places.
Be sceptical!


We discussed Function vs. Method already. A closer look at Operator vs. Operation is highly revealing as well.

Let’s have a look at their definitions at Wikipedia and see if we agree:

Operator (mathematical)
A mapping that acts on elements of a space and produces elements of the same space. (source)

Do you see the implications?

Note how the choice of words in this definition sounds suspiciously uncommon. This is because it carefully tries to avoids assumptions on how an operator could actually work.

  • Why does it say “produce elements” instead of “map to elements”?
  • Why “space” instead of “set”?
  • Why “act on elements” instead of “map elements from”?

Différance! (Jacques Derrida: the difference of words hidden in their “deference” in a given context)

Operation (mathematical)
A calculation from zero or more input values (operands) to an output value (source)

The choice of words is very specific here: “value”, “calculation”, “input” …

  • Q: Doesn’t this sound notably familiar? How is this different from a function?

Note that in Mathematics, an Operator is just a symbol that denotes a specific Operation. Like we use the name of a function (its declaration) to represent its implementation (the function definition). They essentially have the same meaning.

What do we learn for C++ from this?

  • Operators in mathematics are essentially functions with closure (\(X \dots \mapsto X\)).
  • Operators in programming languages are the same as operators in mathematics, plus syntactic sugar.
  • Any function with signature X fun(X, ...) could be called “operator”
  • Finally, any method in C++ X fun(...) defined in class X could be called “operator”

Right?

To confuse the Russians, there are accepted definitions of Operator that explicitly do not imply closure.

Mas sabe el diablo por viejo que por diablo.

When learning to play the piano, there is a specific challenge for adult learners. As adults, we are mentally capable to fully understand concepts and mechanisms before we achieve the competence to apply them correctly. It is not a challenge to understand the principle of a C major chord progression. But it will take weeks of daily exercise to transfer this knowledge to muscle memory and making a technique second nature.

Likewise, there is mental muscle memory for modeling and programming. Take course assignments seriously even if the related concepts seem painfully evident.

We all were beginners once