Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 36 additions & 8 deletions 02cpp1/sec03ObjectOrientedProgramming.md
Original file line number Diff line number Diff line change
Expand Up @@ -152,9 +152,9 @@ int main()
- `getCount()` returns an integer _by value_, so it returns a copy of `count`. We can't modify `count` through this function or the value we get back from it.
- `count` is now private, so if we try to access this directly from outside the class the compiler will raise an error.

## Using Objects for Data Integrity
## Class Invariants and Using OOP for Data Integrity

An extremely useful aspect of defining a new type via a class is the ability to provide guarantees that any object of that type satisfies certain properties. These properties allow programmers to write programs that are more efficient and correct with less overhead for error checking.
An extremely useful aspect of defining a new type via a class is the ability to provide guarantees that any object of that type satisfies certain properties; such properties are often referred to as class _invariants_. These properties allow programmers to write programs that are more efficient and correct with less overhead for error checking.

Let's explore this with some examples.

Expand Down Expand Up @@ -182,7 +182,7 @@ class Ball
};
```

and then we can call the density directly without another calculation. The problem that we now have is that in order for our data to be self-consistent, **a relationship between the radius, mass, and density must be satisfied**.
and then we can call the density directly without another calculation. The problem that we now have is that in order for our data to be self-consistent, **a relationship between the radius, mass, and density must be satisfied**. This kind of relationship is called an **invariant**: a property that must be maintained by all instances of a class. Invariants are very important for writing safe programs, and for being able to reason about the behaviour of programs.

We could approach this problem by calculating the density in the constructor, and making the radius, mass, and density **private**. This means that external code can't change any of these values, and therefore they can't become inconsistent with one another. But we still need to be able to _read_ these variables for our physics simulation, so we'll need to write **getter** functions for them:
```cpp
Expand Down Expand Up @@ -268,7 +268,34 @@ This is particularly bad because we'd expect people to look up books far more of

If our list were _sorted_, then we can search much more quickly using a [binary search](https://en.wikipedia.org/wiki/Binary_search_algorithm). A binary search on a sorted list starts by looking at the element in the middle of the list and checks if the item we're looking for should come before or after that. We then only need to search the half of the list that would contain the book we're looking for. We then apply the same thing again to narrow the list down by half again, and so on. At every step we half the size of the list and therefore the number of titles we have to check is proportional to _the logarithm of the size of the list_. This is much, much better performance, especially if the size of the list is large. A binary search with 21 comparisons could search a list of over a million books!

Of course, we don't want to sort our data before searching it every time (that would be even more wasteful than our linear search), and we want to know with certainty that our list is always sorted, otherwise our binary search could fail. Using an object is a solution: we can define a wrapper class which keeps the list private, and provides an insertion method which guarantees that new entries are inserted into their proper place. Then **we can take advantage of speedier lookup because we know that our catalogue is always in sorted order**. (Incidentally, this would normally be done with a _balanced binary search tree_, an example of which is the C++ `map` type.)
Of course, we don't want to sort our data before searching it every time (that would be even more wasteful than our linear search), and we want to know with certainty that our list is always sorted, otherwise our binary search could fail. Using an object is a solution: we can define a wrapper class which keeps the list private, and provides an insertion method which guarantees that new entries are inserted into their proper place. Then **we can take advantage of speedier lookup because we know that our catalogue is always in sorted order**. In this case our _invariant_ is the property of being sorted, or put more explicitly $i < j \implies x_i \le x_j$. (Incidentally, this would normally be done with a _balanced binary search tree_, an example of which is the C++ `map` type.)

### Reasoning About Class Invariants

From these examples we can see an important pattern arise: an object will maintain the desired property if it is constructed in a state which has that property, and if all permissible operations on the object maintain that property. This is a form of _inductive reasoning_, where the initial construction of the object serves as a base case, and all other possible states of the object are found by the operations on that object (calling member functions or manipulating public data). To design a class where any object of that class maintain a property $P$ then you should:

- Write you constructor so that $P$ is guaranteed for any constructed object. Be wary of uninitialised variable within your class.
- Make any variables `private` if a modification of that variable can alter the property $P$. For example, to maintain a list as being sorted we made the underlying `vector` private because any modification of the data in the array could violate the sorting property. To protect our `Ball` class we made the `mass`, `radius`, and `density` private since modifying any one of these could violate the physical relationship between these parameters.
- Make sure that getters don't return private member variables by reference or through pointers unless there are appropriate `const` protections on the data, as this would otherwise allow unguarded modifications to the state.
- Ensure that any functions that modify the state of the class do not violate the property. In the case of our sorted list, this means that the insertion must update the list in a way that it remains sorted. Be sure to check any setters, as with the `Ball` class: modifying any one of the properties of the ball has consequences for the others. It's a good idea to mark any functions that should not modify the state as `const` so that the compiler can spot any potential risks (see below).

### Protecting State with `const` Members

A member function that is declared `const` cannot modify the state of any member variables in that object. This is a very useful guarantee when reasoning about objects of a given class, and the compiler can help us enforce this.

Consider the getter functions from our `Ball` class, or if we wanted to add a function to print the ball's state. Functions like these should never change the state of the ball itself, so we can mark them as const. We can see an example of this in the following code (the rest of the class definition is omitted for brevity).

```cpp
class Ball
{
public:
double getRadius() const {return radius;}
double getMass() const {return mass;}
double getDensity() const {return density;}
};
```
- The `const` keyword comes after the function name and arguments but before the code block.
- A `const` member function cannot call any other member functions which are not `const`.

## Aside: Organising Class Code in Headers and Source Files

Expand All @@ -277,7 +304,8 @@ As we saw last week, C++ code benefits from a separation of function declaration
In the header file, we should declare the class as well as:
1. What all of its member variables are
2. Function declarations for all of its member functions
3. Can also include full definitions for trivial functions such as getter/setter functions
3. Can also include full definitions for trivial functions such as getter/setter functions if marked `inline`. This can help the compiler optimise these function calls.
4. If a member is marked `const` the keyword needs to appear in both the declaration (header file) and implementation (source file).

For example:
**In `ball.h`:**
Expand All @@ -288,9 +316,9 @@ class Ball
Ball(std::array<double, 3> p, double r, double m);

std::array<double, 3> position;
double getRadius(){return radius;}
double getMass(){return mass;}
double getDensity(){return density;}
inline double getRadius() const {return radius;}
inline double getMass() const {return mass;}
inline double getDensity() const {return density;}

private:
void setDensity();
Expand Down
37 changes: 37 additions & 0 deletions 04cpp3/sec03Templates.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,6 +95,43 @@ vector<T> everyOther(vector<T> &v_in)
- The exact details of the type `T` don't matter in this case, since we never access the data of type `T` anyway. The only restriction on `T` is that it can be added to a vector.
- A function can be generated for every kind of vector in this way.

### Using `auto` with Function Templates

The keyword `auto` can be very useful in function templates. It can be used for variable declarations as well as the output type of a function template where the types will not be known until template substitution takes place. This is particularly useful when you are functions that template over callable objects. A trivial example would be:

```cpp
template<typename F, typename In>
auto apply(F f, In x)
{
auto y = f(x);
return y;
}
```

Here the template parameter `F` can stand in for something callable: a function, `std::function`, lambda-expression, or callable object. From this code we cannot immediately tell what type the result of applying the function to `x` will be, and therefore what the return type of this function is. Using `auto` allows us to write code like this while leaving it to the compiler to infer this type as well as it is able.


In principle we could try to explicitly template over this additional unknown type, as in the code example below.

```cpp
template<typename F, typename In, typename Out>
Out apply2(F f, In x)
{
Out y = f(x);
return y;
}
```

In this case the compiler will fail to infer the type of `Out`. The reason for this is that the compiler needs to be able to deduce all the template parameters _at the call site_. Now that we've introduced an additional parameter `Out`, we would be calling a function like this:

```cpp
int z = apply2(f, x);
```

which would allow the compiler to deduce the types `F` and `In`, but gives no information about `Out` since it does not appear in the arguments. In order to make this version work, we need would need to supply the template parameter explicitly using `<>`.

By contrast, when we use `auto` the compiler can determine at the call site the types of `F` and `In` from the arguments supplied, and can then immediately find or generate the appropriate `apply` function. The type of `y` and the output of the function are then determined with full information about the function rather than just with the limited information at the call site.

## Using Templates with Overloaded Functions

One very useful way to make use of templates is to exploit operator / function overloading. Operators or functions which are "overloaded" can operate on multiple types, for example:
Expand Down
Loading