Skip to content

Commit

Permalink
Merge branch 'mm_reorganisation' of github.com:UCL/research-computing…
Browse files Browse the repository at this point in the history
…-with-cpp into mm_reorganisation
  • Loading branch information
Michael McLeod committed Nov 29, 2023
2 parents fdaf991 + eb5d9ee commit eaf311c
Show file tree
Hide file tree
Showing 16 changed files with 37 additions and 37 deletions.
4 changes: 2 additions & 2 deletions 01projects/sec01Git.md
Original file line number Diff line number Diff line change
Expand Up @@ -132,7 +132,7 @@ Changes to be committed:
new file: hello.cpp
```

Now, all three files are ready to be committed (i.e. made a permanently referencable entity of the project), and we employ the `git commit` command for this.
Now, all three files are ready to be committed (i.e. made a permanently referenceable entity of the project), and we employ the `git commit` command for this.

### `git commit`

Expand Down Expand Up @@ -276,6 +276,6 @@ In fact, this is how we shall proceed with the devcontainer setup for our upcomi

## Further resources

We have only covered a very basic overview of the Git version control system that shall enable us to get started with the in-class exercises and course projects. An excellent resource that provides an expanded introduction is the Software Carpentry's [lessons on Git](https://swcarpentry.github.io/git-novice/) which covers some additional topics such as ignoring certain kind of files from being tracked, referencing previous commits in git commands etc. The sofware carpentry lesson material has been taught as a video playlist with live coding/demonstrator by your course instructor and is available [here](https://www.youtube.com/playlist?list=PLn8I4rGvUPf6qxv2KRN_wK7inXHJH6AIJ).
We have only covered a very basic overview of the Git version control system that shall enable us to get started with the in-class exercises and course projects. An excellent resource that provides an expanded introduction is the Software Carpentry's [lessons on Git](https://swcarpentry.github.io/git-novice/) which covers some additional topics such as ignoring certain kind of files from being tracked, referencing previous commits in git commands etc. The software carpentry lesson material has been taught as a video playlist with live coding/demonstrator by your course instructor and is available [here](https://www.youtube.com/playlist?list=PLn8I4rGvUPf6qxv2KRN_wK7inXHJH6AIJ).

In professional software development, one usually encounters further advanced topics such as branching, rebasing, cherry-picking commits etc for which specialised git resources exist both online and in print. All UCL students have free access to content from LinkedIn Learning, and it is worthwhile to look into some of the [top rated Git courses](https://www.linkedin.com/learning/search?keywords=git&upsellOrderOrigin=default_guest_learning&sortBy=RELEVANCE&entityType=COURSE&softwareNames=Git) there.
2 changes: 1 addition & 1 deletion 02cpp1/sec01Types.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,7 @@ We will focus overwhelmingly on classes as our means of defining custom types, b
- `enum class Colour {red, green, blue};`. This kind of enum (called an `enum class`) cannot be used interchangeably with `int`, and therefore `Colour` can only be used in places that are explicitly expecting a `Colour` type. **We usually want to use an `enum class` so that we don't accidentally mix it up with integer types!**
- This cannot be used to index arrays (because it is not an int), but it can be used as a key in `map` types. `map` and `unordered_map` provide C++ equivalents to Python's dictionary type.
- In order to use these values we have to also include the class name, so we have to write `Colour::red`, `Colour::green`, or `Colour::blue`.
- `union`: Union types are types which represent a value which is one of a finite set of types. A `union` is declared with a list of members of different types, for example `union IntOrString { int i; string s; };` can store an `int` or a `string`. When a variable of type `IntOrString` is declared, it is only allocated enough memory to store _one_ of its members at a time, so it cannot store both `i` and `s` at the same time. The programmer needs to manually keep track of which type is present, often using an auxilliary variable, in order to safely use union types. Given this additional difficulty, **I wouldn't recommend using union types without a very strong reason.**
- `union`: Union types are types which represent a value which is one of a finite set of types. A `union` is declared with a list of members of different types, for example `union IntOrString { int i; string s; };` can store an `int` or a `string`. When a variable of type `IntOrString` is declared, it is only allocated enough memory to store _one_ of its members at a time, so it cannot store both `i` and `s` at the same time. The programmer needs to manually keep track of which type is present, often using an auxiliary variable, in order to safely use union types. Given this additional difficulty, **I wouldn't recommend using union types without a very strong reason.**

Microsoft has excellent, and accessible, resources on [`enum`](https://learn.microsoft.com/en-us/cpp/cpp/enumerations-cpp?view=msvc-170) and [`union`](https://learn.microsoft.com/en-us/cpp/cpp/unions?view=msvc-170) types if you are interested in learning more about them.

Expand Down
2 changes: 1 addition & 1 deletion 02cpp1/sec02PassByValueOrReference.md
Original file line number Diff line number Diff line change
Expand Up @@ -222,7 +222,7 @@ When we use a `return` statement in a function, we are also passing by value, al
- Objects are copied using their _copy constructor_, a special function in their class definition which defines how to create a new object and copy the current object's data. (In many cases this can be automatically created by the compiler.)
- Some objects also have a _move constructor_ defined, in which the data is not explicitly copied, but a new object takes control of the data. We'll return to this idea when we talk about pointers later in the course. (The move constructor may also be automatically created by the compiler.)
- Normally when an variable goes out of scope its memory is freed and can be reallocated to new variables. If we have a _local variable_ in the function scope that we want to return, we can't just give the address of the data (return by reference) because when the function returns the variable will go out of scope and that memory is freed.
- Although return types can be refences, e.g. `int& someFunction()`, you have to be absolutely certain that the memory you are referencing will remain in scope. This could be e.g. a global variable, or a member of a class for an object which continues to exist. It should _never_ be a variable created locally in that function scope. Don't use reference return types unless you are really confident that you know what you are doing!
- Although return types can be references, e.g. `int& someFunction()`, you have to be absolutely certain that the memory you are referencing will remain in scope. This could be e.g. a global variable, or a member of a class for an object which continues to exist. It should _never_ be a variable created locally in that function scope. Don't use reference return types unless you are really confident that you know what you are doing!
- For classes with a move constructor a local object can be returned without making a copy, since the compiler knows that the object is about to be destroyed as soon as the function returns, and can therefore have its data transferred instead. (This is why this optimisation can be used when returning a value but _not_ when passing an object to a function by value: when passing an object to a function the original object will continue to exist.)
- **The compiler will use a move constructor when available if the object is deemed large enough for the move to be more efficient than a copy, and a copy constructor when not.** Therefore, you may find that returning values is more performant than you expect from the size of the data-structure.
Expand Down
10 changes: 5 additions & 5 deletions 02cpp1/sec03ObjectOrientedProgramming.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ Estimated Reading Time: 60 minutes

# Custom Types and Object Oriented Programming (OOP) in C++

As a programming lanaguage, C++ supports multiple styles of programming, but it is generally known for _object oriented programming_, often abbreviated as _OOP_. This is handled in C++, as in many languages, through the use of classes: special datastructures which have both member data (variables that each object of that class contains and which are usually different for each object) and member functions, which are functions which can be called through an object and which have access to both the arguments passed to it _and_ the member variables of that object.
As a programming language, C++ supports multiple styles of programming, but it is generally known for _object oriented programming_, often abbreviated as _OOP_. This is handled in C++, as in many languages, through the use of classes: special datastructures which have both member data (variables that each object of that class contains and which are usually different for each object) and member functions, which are functions which can be called through an object and which have access to both the arguments passed to it _and_ the member variables of that object.

We have already been making extensive use of classes when working with C++. Indeed, it is difficult not to! The addition of classes was the main paradigm shift between C, a procedural programming language with no native support for OOP, and C++.

Expand Down Expand Up @@ -98,7 +98,7 @@ int main()
}

```
- The count is incremented in the constuctor (`countedClass()`), and so increased every time an instance of this type is created.
- The count is incremented in the constructor (`countedClass()`), and so increased every time an instance of this type is created.
- The count is decremented in the destructor (`~countedClass()`), and so decreased every time an instance of this type is destroyed.
- `count` is a static variable, so belongs to the class as a whole. There is one variable `count` for the whole class, regardless of how many instances there are. The class still accesses it as a normal member variable.
- `count` also needs to be declared outside of the class definition. (This is where you should initialise the value.)
Expand Down Expand Up @@ -250,7 +250,7 @@ class Ball
```
We now have a ball class that can be instantiated with any mass and radius, and can have its mass or radius changed, but **always satisfies the property that the density field is correct for the given radius and mass of the object**. Being able to guarantee properties of objects of a given type makes the type system far more powerful and gives users the opportunity to use objects in more efficient ways without having to check for conditions that are already guaranteed by the object's design.

### Maintaining Desireable Properties
### Maintaining Desirable Properties

Consider another example where we have a catalogue for a library. To keep things simple, we'll say that we just store the title of each book. Very simply, we could define this as a vector:
```cpp
Expand Down Expand Up @@ -395,9 +395,9 @@ Function overriding is fundamental to this polymorphic style of programming beca

## Polymorphism

Polymorphism is the ability to use multiple types in the same context in our program; in order to achieve this we must only access the common properties of those types through some shared interface. The most common way to do this is to define a base class which defines the necessary common properties, and then have sub-classes which inherit from the base class which represent different kinds of objects which can implement this interface. This is caled *sub-type polymorphism*, and is one of the most common forms of polymorphism.
Polymorphism is the ability to use multiple types in the same context in our program; in order to achieve this we must only access the common properties of those types through some shared interface. The most common way to do this is to define a base class which defines the necessary common properties, and then have sub-classes which inherit from the base class which represent different kinds of objects which can implement this interface. This is called *sub-type polymorphism*, and is one of the most common forms of polymorphism.

By exploring polymorphism we can also understand the behaviour, and some of the limitations, of the straightforward model of inheritence that we have used so far.
By exploring polymorphism we can also understand the behaviour, and some of the limitations, of the straightforward model of inheritance that we have used so far.

Let's assume that we have some class `Shape`, and derived classes `Circle` and `Square`.

Expand Down
2 changes: 1 addition & 1 deletion 02cpp1/sec04StandardLibrary.md
Original file line number Diff line number Diff line change
Expand Up @@ -350,7 +350,7 @@ We can see from our previous example the use of the `()` and `{}` brackets to de
You will often find when programming, especially in a language with such an expansive standard library, that there are things that you need to look up. There are a large number of classes and functions available to C++ programmers, many of which may be new to you or require refreshing at various points.
Two common sites for C++ refernce are:
Two common sites for C++ reference are:
- <https://cplusplus.com/>
- <https://en.cppreference.com/>
Expand Down
6 changes: 3 additions & 3 deletions 03cpp2/sec01Exceptions.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ We'll take a look now at how to do this in practice, starting with catching exce

## Catching Exceptions

We'll start by looking at how to handle an error thrown by an existing function, such as a range error thrown by a vector. When such a function encounters an erorr and _throws_ an exception, it needs to be _caught_.
We'll start by looking at how to handle an error thrown by an existing function, such as a range error thrown by a vector. When such a function encounters an error and _throws_ an exception, it needs to be _caught_.

- We first need to identify the code that could throw the exception. We do this with the `try{...}` keyword.
- This tells our compiler that we want to monitor the execution of this code block (inside the `{}`) for exceptions.
Expand Down Expand Up @@ -90,7 +90,7 @@ int main()
- `catch` clauses will be evaluated in order, so you should always list your `catch` statements from most specific to most general i.e. list _derived classes_ before the _base classes_ from which they inherit. For example, `std::out_of_range` is a sub-type of `std::exception` since the `out_of_range` class inherits from `exception`. This means that:
- if `catch(std::exception e)` comes before `catch(std::out_of_range e)` then all `out_of_range` errors will be caught by the more general `exception` clause, and the specialised `out_of_range` error handling code will never run.
- if `catch(std::out_of_range)` is placed first, then the `catch(std::exception e)` code will only run for exceptions which are not `out_of_range`.
- `cerr` is a special output stream for errors; we can use this if we want the error to be written to a different place than standard output (e.g. standard ouput to file and errors to terminal, or vice versa). We can also output exception information to `cout` though.
- `cerr` is a special output stream for errors; we can use this if we want the error to be written to a different place than standard output (e.g. standard output to file and errors to terminal, or vice versa). We can also output exception information to `cout` though.

We can see in this example that using `try` and `catch` blocks have significant advantages for someone reading our code:

Expand Down Expand Up @@ -215,7 +215,7 @@ int main()

## Defining Our Own Exceptions

We've mentioned above that we can differentiate between different kinds of exceptions by checking for different expception classes, and then execute different error handling code accordingly. This is a very powerful feature of exceptions that we can extend further by defining our own exception classes to represent cases specific to our own applications. When we define our own exceptions, they should inherit from the `std::exception` class, or from another class which derives from `std::exception` like the standard library exceptions listed above. You should be aware though that if you inherit from a class, for example `runtime_error`, then your exception will be caught by any `catch` statements that catch exceptions of the base classes (`runtime_error` or `exception`).
We've mentioned above that we can differentiate between different kinds of exceptions by checking for different exception classes, and then execute different error handling code accordingly. This is a very powerful feature of exceptions that we can extend further by defining our own exception classes to represent cases specific to our own applications. When we define our own exceptions, they should inherit from the `std::exception` class, or from another class which derives from `std::exception` like the standard library exceptions listed above. You should be aware though that if you inherit from a class, for example `runtime_error`, then your exception will be caught by any `catch` statements that catch exceptions of the base classes (`runtime_error` or `exception`).

Exceptions that we define should be indicative of the kind of error that occur. Rather than trying to create a different exception for each function that can go wrong, create exception classes that represent kinds of problems, and these exceptions may be thrown by many functions. When creating new exception classes it is a good idea to think about what is useful for you to be able to differentiate between.

Expand Down
Loading

0 comments on commit eaf311c

Please sign in to comment.