Quiz review CC-BY-NC

Maintainer: admin

The notes below are based on the slides created by the lecturer (Dan Pomerantz), which can be found on the course website.

1Quiz 1

The first quiz will take place on Tuesday, February 26, during class (at the usual location). It will be 50 minutes long and will cover everything up to and including lecture 7, except for inheritance.

Other resources:

1.1Lecture 1: Introduction

Link to slides

  • C++ = C with classes
  • Compile-time type-checking, mostly compatible with C
  • In C++ but not in C:
    • Classes
    • Overloading
    • Templates
    • Exceptions
    • Namespaces
  • In C++ but not in Java:
    • Compiles to machine code, not VM code
    • Multiple inheritance is allowed
    • Pointers and references (done very differently from Java)
    • No garbage collection - memory management must be done manually
  • The hello world program:
    • #include <iostream> to have access to std
    • int main() as usual, should return 0 for success
    • std::cout << "lol"; to print something (printf works too, though you have to include stdio.h in that case)
    • std::cout is an object of the class ostream; similar to the stdout global from C
    • << is overloaded to act as both a bitshift operator and a stream-appending operator
    • It returns a stream, so you can chain multiple <<s together
    • To compile, you can do g++ -Wall helloworld.cpp -o outputfile where -Wall means that you get all the warnings
  • Types
    • bool
    • signed, unsigned, regular char
    • int: short, long, unsigned or signed, etc
    • float, double
    • long double
  • Operators and statements
    • Basically the same as C. Don't worry about it.
  • Scope
    • global scope, local scope, block scope (C++ has this because it's not Javascript)
  • Preprocessor
    • #define, #include, #ifdef, etc

1.2Lecture 2: Basics of C++

Link to slides

  • Function orders
    • You can't call a function before its defined
    • Defining the function header works (so if you put everything in a header file, you can call anything in whatever order you like)
  • Preprocessor commands
    • #define, for defining macros
      • Textual substition (basically find & replace), uses capital letters by convention
      • Can even use it for functions, like: `#define MAX(a, b) ((a < b) ? b : a)
    • #include
      • Copies & pastes the content of the file into the output file
      • Usually header filenames end with .h, but only by convention
      • "header.h" -> searches local dir, then standard directories
      • <standard.h> -> only searches standard dirs
    • #if, #ifdef, #ifndef, #if defined
    • You can also use standard boolean operators in preprocessor statements (&&, ||), and you can nest statements
    • undef deletes a macro
  • Some operators
    • sizeof something - return the number of bytes occupied by something (could be a type, too)
    • comma operator: exp1, exp2 evaluates both, returns exp2
    • Assignment returns the assigned value, so you can put an assignment in another assignment
  • Some keywords, used when declaring things
    • auto: implicit in any variable declaration (for temp storage); don't know why we need this then
    • volatile: warns compiler that the value may change unexpectedly (used for e.g., semaphores)
    • register: tells compiler that this variable will be used frequently (may be ignored by compiler)
    • const: can't modify after initialisation (compiler will complain), and must be initialised at declaration
    • extern: declares something that isn't initialised until later on (or in another file that is being included); can be used in conjunction with const to prevent the compiler from complaining
    • static: several different meanings, depending on the context:
      • outside a function, this makes the variable local so that it can't be used by other files
      • inside a function, this allocates the memory for it on the heapi think so that it sticks around until the end of the program and not just when it goes out of scope
      • also, used inside a loop, it can prevent a variable from being re-initialised (only set the first time)
      • it can also be used for OOP, but we'll probably cover that later
  • Arrays
    • Don't need to specify the length of the array if initialising explicitly (string, or {1, 2, 3}, etc)
  • Function definitions
    • No nesting
    • If it doesn't return anything, should return void in the header
    • Inline functions: used for small functions; asks the compiler to use macro substitutions to avoid actually calling it (the compiler may or may not accede)
    • Parameters of "simple" types (int, bool, char, etc) are passed by value
  • User-defined types
    • enums
      • you can make a named type, or omit the type (then they can be used anywhere)
      • enum season { WINTER, SPRING, SUMMER, FALL} and enum season this_season = WINTER
      • each enum value is implicitly assigned an int, starting from 0, though you can set explicit values with equals or just set the first one
      • enum grade { A=4, B=3, C=2, D=1 } or enum grade { D=1, C, B, A } (equivalent)
    • structs: same as in C. struct something { // define properties }; then struct something var;, or make an array of these, or whatever
    • union: sort of like a struct, but all fields share the same place in memory (legitimate examples are hard to come by, but it's usually when you want to save space I guess)
    • the typedef operator: typedef unsigned short word makes word a shorthand for unsigned short (can also be used for structs - typedef struct _Etc {} etc makes etc a shorthand for struct _Etc)
  • Namespaces
    • like std::cout (std is the namespace)
    • Pretty simple concept
    • To stay within the std scope (to avoid having to use std::etc every time), use using namespace std;
    • Or, using std::cout; to get only cout (like from x import y in python as opposed to from x import *, although in the case of C++ we can still use std::cout, so we can consider #include <iostream> as doing import x)
    • To define a namespace: namespace A { // define functions here }

1.3Lecture 3: Pointers and references

Link to slides

  • Pointers
    • int* p or int * p or int *p all mean that p points to something of type int
    • *: unary dereference operator (gets whatever is stored at that address)
    • &: gets the address of a variable
    • Pointer arithmetic:
      • integer = pointer1 - pointer2 gets the number of elements between the two pointers (which MUST be of the same base type, in the same array?)
      • (I guess the pointers are by element and not by byte. Weird)
    • Useful for modifying function params (the function must take a pointer, and must be passed a pointer)
    • Pointers to structs/unions:
      • Probably faster than passing by value or whatever happens normally
      • Declare an instance of a struct, get its address with &, save as a pointer
      • To access a field on a struct instance, do (*p).field or p->field (-> only in C++)
    • Things to avoid
      • Failing to initialise *p and then attempting to do *p = 1
      • Returning pointers to local variables (well duh)
      • Comparing pointers when we really want to compare the data the pointers are pointing to
  • Memory allocation
    • Use the new keyword to prevent local vars from going out of scope
    • Must delete using delete to prevent memory leaks
  • References
    • int x; int & y = x; means that y is an alias for x
    • So, if we change y, we change x as well (they are pointing to the same cell in memory, etc)
    • Must be initialised upon declaration
    • Must have the same type as the thing it's referencing
    • Cannot be reassigned after initialisation
    • Can't have references to references
    • Useful for function parameters (callee can pass in regular variables, and the function can change the values of them without having to use pointers at all)
    • Taking a reference whose type is some large data structure is faster than being passed it by value, as we can avoid having to copy it
    • Using const in a function def for a ref parameter means that we can't modify the contents of the ref
    • If you try to pass something that is not a variable of some sort (an lvalue) to a function expecting a reference, it will fail (compiler error)
    • Same if you pass something of the wrong type
    • More on lvalues:
      • These are: x, something->x, something.x, *p, array[1]
      • These are not: x+1, p (where p is a pointer), lol() (unless it returns a reference)
    • References as return values:
      • Can be used to return a large object without copying it (should actually the pointer)
      • Or, to return an lvalue
      • To do this, just return the thing you want a reference to, and put & in the function header directly after the return type

1.4Lecture 4: Memory management

Link to slides

  • static vs automatic (permanent storage vs ephemeral, in-scope storage)
  • In C, to dynamically allocate memory on the heap, use malloc and free
  • In C++, we also have new and delete (unary operators, not functions)
  • new returns a pointer; delete takes in a pointer (if the pointer is NULL, it does nothing)
    • We only need to delete things created with new, so delete must be passed a pointer that was returned by using new
    • new implicitly calls the constructor; we can specifiy arguments for it if we like, in the usual way
    • if new fails, it throws an exception, which terminates the program
    • the exception can be surpressed by doing new(std::nothrow) (gotta #include <new>; in that case, if it fails, it will just return NULL
  • new and delete on arrays:
    • If we need to determine an array length at runtime, we have to use the new operator (int array = new int[x])
    • Of course, that then has to be deleted, using delete [] array (note the [])
    • Doing it this way initialises the elements to 0 or something (if we just did int array[100], the elements could be anything)
  • Initialising multidimensional arrays
    • Can't just do new int[5][5]; have to initialise each element in the first outer to a new int[5]
  • Advantages of new/delete over malloc/free
    • No need to cast pointers - new always returns the right type
    • Don't need to calculate the size of the object
    • new implicitly calls constructor
    • Exceptions
    • delete implicitly calls destructor
    • new can be overridden (is this a good thing???)
  • Possible issues with new/delete
    • Trying to delete a pointer that was not returned by new will result in a segfault
    • Trying to delete the same pointer twice will result in this cryptic error: *** glibc detected *** ./a.out: double free or corruption (fasttop): 0x08618008 ***
    • Not deleting things created with new (memory leaks)
    • In the slides there is an example on page 14 titled "Assuming the memory is initialised"
      • However, this is NOT an example of a common memory management error, at least not with my compiler (gcc 4.6)
      • pv1[0] is indeed defined
      • In fact, the elements are all initialised to 0
      • Even sum has been implicitly initialised to 0.0, which makes sum += pv1[0] work
      • Not sure if it's a mistake in the slides or something compiler-specific

1.5Lecture 5: The standard library

Link to slides

  • I/O
    • cin, cout, and cerr are stream objects which supersede stdin, stdout and stderr respectively (they're easier to type I guess)
    • [somestream] << [some string] is basically a replacement of fprintf([some stream], [some string]) though without the formatting options maybe
    • Remember that << is LTR, when chaining (as it should be)
    • To read from cin or something, use >> (returns the stream, again)
    • To do the equivalent of string formatting with streams, use output stream manipulators
      • Examples: hex, oct, dec to control the base; setprecision(2) which is sort of like %.2f; fixed to set fixed precision in some shape or form
      • To use these, have to #include <iomanip>
      • Some manipulators are temporary, in which case, they only apply to the next item printed
    • For I/O with normal files, #include <fstream> which gives ofstream (files we can write to) and ifstream (files we can read from)
      • To write to a file: ofstream file; file.open('file'); file << "ll"; file.close()
      • Going to skip the reading example, but it's basically ifstream instead of ofstream and with some other stuff that we probably don't need to know
      • Remember that you can use strings to hold input in C++, and don't need to rely on buffers
      • Other functions: file.get(ch) reads a character (byte) into ch; file.unget puts it back into the stream somehow (?); getline(file, line) is self-explanatory;
      • stream.get() can take a second parameter, which is the number of bytes we want to read
      • We can implement random access if we want, using seekp, tellg, etc
  • Container classes in the standard lib
    • They're all templated classes (i.e. generic, can operate on any type)
    • Although when you instantiate a particular class, you need to fix the type
    • vector<int> x creates a vector which holds ints (need to #include<vector> and using namespace std
    • Then, you can call methods on x, etc
    • Iterators
      • for (vector<int>::iterator current = x.begin(); current != x.end(); current++)
      • note that x.end() is actually one past the end, to ensure that we reach the last element
      • Also, the size of the container (number of elements) is x.end() - x.begin()
      • Then, to get the data, just do *current since current basically acts like a pointer
      • Note that if the container has been consted, you need to use vector<int>::const_iterator (and the container ends up being read-only)
      • Beyond read-only, we also have write-only, forward-only, bidirectional, random-access iterators
      • begin() returns a random-access iterator (we could jump ahead any number of elements)
    • Generic iterators
      • have to put template class<Iterator> somewhere
      • then, instead of doing vector<int>::iterator, can just do Iterator (the type determined and checked at compile time)

1.6Lecture 6: Introduction to C++ classes

Link to slides

  • Header declarations for classes
    • class whatever
    • public: // public declarations here (including the constructor usually)
    • private: // etc
    • if the return type is something declared in the class, have to use whatever::thetype in the method signature
  • Constructors
    • If you do whatever x(0) you get a whatever object; if you want a pointer, do whatever* x = new whatever(0) (both call the constructor with the parameter 0)
    • If you omit the () part, you call the default constructor which doesn't really do much
  • Access modifiers
    • public, private (only members of the class), protected (this class and classes that inherit from it)
  • this
    • Pointer to the current object (rarely necessary to use)
  • const methods
    • put a const right before the { to declare that the method will not change anything
    • not sure if this is reinforced by the compiler
  • static
    • for functions: no this
    • for data: shared among all instances of the class (usage example: a counter of how many objects of this class exist)
  • Default parameters
    • like kwargs in python except you have to specify the type
  • Destructors
    • Called when an object is deleted OR when it goes out of scope
    • define it by ~whatever()
  • Copy constructors
    • When we try to copy an object, we make a shallow clone
    • To prevent this, we can create our own copy constructor which does a deep clone
    • whatever(const whatever & source)
  • friend functions lol
    • To allow other classes to access private members
    • friend returntype somefunction(...) should be added to the whateverclass def
    • then somefunction has access to the private members of whatever
    • Also applicable to member functions, and even classes
  • Scoping issues
    • use this-> if you need to refer to a private member that is shadowed by a local variable
    • Use ::x to refer to a global object and not the x defined in the class
    • Nested classes are not visible outside of that scope

2Quiz 2

The second quiz will take place on Tuesday, April 2, during class (at the usual location). It will be 50 minutes long and will cover everything up to and including lecture 11, though the last few lectures will be the focus.

The notes below are based on the slides, which can be found on the course website

Other resources:

Since official solutions are not provided, you can find student-generated solutions below

2.1Lecture 8: OOP and inheritance

Link to slides

Purpose of a class:

  • Abstract data type (functionality, data)
  • Hide implementation detail
  • Inherit functionality

2.1.1Recap of classes

To recap, here's how you declare a class (usually in a header file):

class Person {
public:
    Person(); // Default constructor
    Person(string name, float money); // Full constructor
    Person* clone_self();
    Person* reproduce(const Person &other_parent);
    void donate_money(float amount, Person &beneficiary); // This works even if money is private
    string get_name();

private:
    string name;
    float money;
}

You can define the functions in the non-header file:

Person::Person() {
    this->name = "John Doe";
}

Person::Person(string name, float money) {
    this->name = name;
    this->money = money;
}

Person* Person::clone_self() {
    return new Person(this->name, this->money);
}

Then you can instantiate people and call methods:

Person* alice = new Person("Alice Doe", 100);
Person* bob = new Person("Bob Doe", 100);
Person* alice_bob = alice->reproduce(*bob);
// ^ doesn't really make sense but whatever

cout << alice_bob->get_name();
alice->donate_money(50, *alice_bob);
bob->donate_money(50, *alice_bob);

2.1.2Inheritance

  • Derived classes inherit all data and methods, and can add new ones or override inherited
  • Privacy of methods and data:
    • Private: only visible to that class
    • Protected: visible to derived classes as well
    • Public: visible to everybody
  • Privacy of inheritance:
    • Usually you do class B: public A
    • If the public is omitted, it is equivalent to class B: private A
    • Pretty much the same as above except in this case the privacy refers to the fact that B is inheriting from A
      • If public, everyone can know that B inherits from A
      • If protected, only children can know that B inherits from A. Thus only children will be able to access the public methods/data that B inherits from A
      • If private, only B knows that B inherits from A ... thus only B can access the public or protected methods/data that B inherits from A
      • You might use private inheritance when B relies on A in terms of implementation, but outsiders don't need to know that B uses A (e.g., a graph implemented using an array for an adjacency list)
    • In other words, public inheritance gives the default access modifier behaviour
    • Protected inheritance makes public things protected
    • Private inheritance makes public and protected things private
  • No super, due to multiple inheritance
  • Constructors and destructors:
    • Automatically inherited
    • Base class constructors are always invoked, before derived class constructors
    • Base class destructors are always invoked, after derived class destructors (and the derived class destructors are only invoked if the base destructor is virtual)
    • The default constructors for base classes are invoked by default, but we can invoke a different constructor by inheriting from it: B(int x) : A(x) { // ... }, p. cool
    • (Obviously this is not true of other methods)
  • Assignment compatibility:
    • Assigning something of type B to something of type A is legal, but it makes you lose anything defined only in B
    • A a; B b = a is illegal since B-only data would be undefined
    • Using pointers, though, we can do A *pa = new B;
  • Polymorphism
    • Doesn't happen by default - calling *pa->something() when something is defined for A and overridden for B would still call the method in the base class
    • To avoid this, define the base class method to be virtual (before the return type)
    • Note that a derived class method will only override a virtual function if the type sigs match
    • virtual is legal in derived classes as well, of course
    • You can choose which particular method to use using the scope operator: pa->A::something to call the base class (when it's virtual)
    • Constructors cannot be virtual; destructors can (and probably should be)
  • Abstract classes:
    • At least one function is virtual and unimplemented/pure (virtual void something() = 0;)
    • Abstract classes can not be instantiated directly - you can only instantiate an inheriting class that is not abstract

2.2Lecture 9: Inheritance continued

Link to slides

2.2.1Static and dynamic dispatch

  • If a member function is virtual, choosing which function to run is done at runtime (dynamic dispatch)
    • Implemented using a vtable (contains addresses of virtual functions)
    • At runtime, the call to the function is executed by "indirecting" through the vtable pointer
    • I don't really know how this works
    • But a function in the base class can call methods that are pure as long as they're defined in a derived class (and obviously the base class can't be instantiated)
  • If it's not virtual, then the function called depends on the type of object (static dispatch)
    • Calls within a constructor or destructor are always static (so, always referring to the method accessible within the class)

2.2.2Multiple inheritance

  • Akin to the interface construct in Java, except messier
  • Syntax: class C : public A, private B
    • Then an object of type C can be assigned to an object of type A or B, though you might lose stuff
    • Assignment compatibility with pointers is the same as with single inheritance
      • However, pointers of different types to the same object will probably have different values, due to the way objects are stored (A|B|C) - the compiler returns a pointer to the beginning of the part corresponding to the type of the object
  • Ambiguities
    • Let's say f is defined in both A and B, and something in C calls f
      • This is somewhat ambiguous - is it A::f or B::f which is called?
      • You'd think that it would be resolved by the order in which the base classes are inherited from, but no, you just get a compiler error
      • Same thing happens for data
    • Diamond inheritance: A, B1 and B2 inherit from A, C inherits from B1 and B2
      • Then something of type C basically has two copies of type A stuff
      • Assignment compatibility is frail when you try to assign to something of type A, because which A do you use? (could avoid this by casting)
      • Scope resolution can be used to avoid the "which method do you use" problem
      • Try to avoid this situation
        • Use virtual inheritance if necessary: class B1 : virtual public A, class B2 : virtual public A; ensures that only one copy of the common base class is used
        • Avoid function name conflicts (data name conflicts are fine I guess)

2.2.3Advanced type-casting

Dynamic casting: takes runtime type into account. Say C inherits from A and B, A *pa1 = new A, A *pa2 = new C. Then:

  • dynamic_cast<C*>(pa1): returns null - you can't safely convert this to a C (missing B parts)
  • dynamic_cast<C*>(pa2): works, returning something of type C
  • dynamic_cast<B*>(pa2): works, returning something of type B (trims off the A parts I suppose)

Static casting: only the compile-time type is considered.

  • static_cast<C*>(pa1): works, but is probably a bad idea since the B parts are missing
  • static_cast<C*>(pa2): works, returning something of type C (same as dynamic casting)
  • static_cast<B*>(new C): works, returning something of type B
  • static_cast<B*>(pa2): compiler error, because pa2 is technically (statically) of type A, which is not related to B as far as the compiler knows

reinterpret_cast<desired type>(expression): no checks at all. Pretty dangerous.

2.2.4Remarks on algorithms.h

If you want to use, say, sort from algorithms.h, you'll have to pass in a function that defines the comparison. Sometimes, you'll want the function to be able to take other arguments. In that case, you'll need to define the function as part of a class; you can then pass other arguments to the constructor. Then you'll have to define a method called operator()(arguments) (the return type should be whatever you want your function to return - in this case, bool). To pass the function as an argument to sort, do this: MyClass f(arguments) then sort(..., ..., f).

2.3Lecture 10: Overloading operators and exceptions

Link to slides

2.3.1Operator overloading

Syntax: [type] operator [sign] ([arguments]) where operator is the word operator, [type] is the return type as usual, and [sign] is the operator we want to overload (e.g., +, or [] for array-like operations, or = for assignment, or == for comparison)

2.3.2Exceptions

  • try, catch, throw (no finally)
  • catch can take in parameters of any type, or it can just be catch (...) including the ellipsis for the default handler
    • it must always specify the type, though it doesn't have to specify a name (in which case we can't examine the exception more closely of course - anonymous)
    • the default handler always behaves anonymously (even the type is unknown)
  • methods don't have to specify the exceptions that they can throw, unlike in Java, though they may if they like
    • if a method does specify throw, it's assumed that it can only throw the types given
    • bool f() throw() means it throws no exceptions, bool f() throw(int) means it can only throw int
    • Throwing types beyond those specified is legal (compiler-wise), but those exceptions can't be caught; the program will just terminate
  • Some obvious things: try/catch blocks can be nested; exceptions can cascade through multiple function calls
  • When an exception is thrown:
    • the call stack is unwound
    • all fully-constructed objects (NOT created with new) are destroyed
    • Those allocated with new are not destroyed
  • You can pass execptions upward after catching them, simply by using throw within a catch block
  • Exceptions can be class types (inheritance can be used, too)
    • Only one catch block is executed, and the first compatible one is chosen
    • To avoid problems with assignment involving derived/base classes, we can use references: catch (SomeException &e)
    • References are recommended over pointers, as you don't have to worry about deleting them then
  • Don't throw exceptions in a destructor. Why? Probably because you don't know when destructors will be executed (since they are called whenever an object goes out of scope)
  • Exceptions that can be thrown by the std lib are defined in stdexcept

2.3.3Resource acquisition is initialisation

This is a design pattern used in C++ to guarantee that resources are freed when a function either returns normally or throws an exception. This is done by putting resource-freeing code in the destructor, which will be called whenever the object goes out of scope. Of course, for this to occur, relevant objects must be local, not global. Acquiring resouces should occur in the constructor.

Incidentally, it is possible to enclose entire functions within a try/catch block:

void f() try {
    // something
} catch (...) {
    // something else
} {
    // presumably the actual function body goes here
}

2.4Lecture 11: Templates and custom iterators

Link to slides

2.4.1Custom iterators

Example:

class FibonacciIterator {
private:
    int next_position;
    int current_position;
    vector<int>* data;

public:
    FibonacciIterator(vector<int>* data) {
        this->data = data;
        this->current_position = 0;
        this->next_position = 1;
    }

    FibonacciIterator& operator=(const FibonacciIterator& other) {
        this->data = other.data;
        this->current_position = other.current_position;
        this->next_position = other.next_position;
    }

    // Prefix ++ operator (++i)
    FibonacciIterator& operator++() {
        int temp = next_position;
        this->next_position += current_position;
        this->current_position = temp;

        return *this;
    }

    // Postfix ++ operator (i++)
    FibonacciIterator& operator++(int) {
        int temp = next_position;
        this->next_position += current_position;
        this->current_position = temp;

        return *this;
    }

    // Dereference operator
    int& operator*() {
        return (*data)[current_position];
    }
}

haven't tested it

2.4.2Templates

#include<iostream>
#include<string>

using namespace std;

/*
This is a fairly useless class for a container that holds exactly one object at
a time. Although it does keep track of how many objects it has held so far, which could possibly come in handy one day.
*/
template <class T> class Container {
private:
    T* item;
    int num;
public:
    Container() {
        this->num = 0;
    }

    void fill_with(T item) {
        this->num++;
        this->item = &item;
    }

    T retrieve_item() {
        return *item;
    }
};

int main() {
    Container<string> container;
    string item = "Lol";
    container.fill_with(item);
    cout << container.retrieve_item();
}

If we wanted to define the member functions outside the class, we would have to do something like

template <class T> T Container<T>::retrieve_item() {
    return *item;
}

Multiple template types can be used as well:

template <class T, class U, class V> class TripleContainer {
    // ...
}

We can also use integer constants (<int N>) and even other templates (<template <class> class A> though I'm not sure why you would do that).

If two Containers are instantiated with different template types, they are usually not assignment compatible, though they can be if the types are essentially equivalent.

If you try to instantiate a class with a template type that is not supported (say, if the class tries to perform arithmetic with things of that type, but you give it something that does not support arithmetic), you'll get a runtime error (usually not compile-time).

Functions can also be defined using templates:

template <class T> void some_function(int x) {
    // ...
}

We can mark templates for export by putting the export keyword in front of a template def, but this isn't supported by many compilers so why does it even matter?

Template specialisation allows us to write code that only works for a particular template type.why does this mean

The typename keyword can be used instead of class in a template def to reduce confusion (recently introduced).

Note that inheritance is not preserved across templates if we use a class as a template type. Speaking of inheritance, did you know that templates can inherit from templated classes? Same deal as regular inheritance. For example:

template <class T> class B : public A<T> {
    // ...
};

Templates can also be members of a class.

2.5Practice question solutions

2.5.1Practice questions

Link to questions

1: Is a: inherits from. Has a: should be a class member.
2: Encapsulation, preventing access to attributes, hiding implementation details, allows for inheritance, etc
3: Defines relationships (more semantic), saves typing, allows for polymorphism, etc
4:

// Let data be the vector<float>
int n = 0;
for (vector<float>::const_iterator current = data.begin(); current != data.end(); current++) {
    n += *current > 0;
}

5:

// Just going to define the bare minimum for now
class IntLinkedListIterator {
private:
    Node* current;
public:
    IntLinkedListIterator(Node* node) {
        current = node;
    }

    // Postfix operator (i++)
    IntLinkedListIterator& operator++(int) {
        current = current->next;

        // Not really necessary in this case but it's convention
        return *this;
    }

    // Dereference operator
    int operator*() {
        return current->value;
    }
}

6: I haven't looked at the assignment but I'm assuming it would be misleading because we don't actually have O(1) random access. Not a good idea with a linked list iterator. To implement, just use a loop (while or for)
7: Unexpected input or result, want to allow something else to handle it
8: Too long
9: ^

2.5.2Winter 2010 quiz

Link to questions

  1. 1 then 2 on the next line. Why: since A::f is virtual and the B::f is defined, then A::f is never called when calling pa->f(). Similarly, when calling pb->f(), only C::f is called since A::f is virtual (B::f doesn't even need to be virtual)
  2. 1 then 2 on the next line. Why: 1 is thrown as an exception; it is caught and printed out. Then, it is incremented by 1, and is thrown again, after which is it caught and printed.
  3. 1 then 0 because both destructors are called, since the A destructor is virtual (thus ~B is called first, then ~A).
  4. 0 only because B the A destructor is not virtual.
  5. Not 1, not 2, yes 3, yes 4, yes 5, no 6.

2.5.3Winter 2012 quiz

Link to questions

  1. Normal, virtual (can be overriden), pure (must be overriden).
  2. a) totally fine, though you will lose any apple-only things. b) fine, and you won't lose any apple-only things.
  3. Private: only members of that class can access/modify it. Protected: inheriting classes can too. Public: anyone can. Bad practice because sometimes you don't want others to be able to access/modify it (and it hides the implementation, which is less likely to cause issues with backwards compatibility if you ever modify the implementation, etc).
  4. The vector of ints vector<int>, the current position (int)
  5. current += x;?
  6. Define an exception called NoRecordFound or something, then throw it if you can't find a record