Review for midterm

Classes

class name {
  public:
    // data members
    // methods
  private:
    // private methods...
};

A class creates a template for new objects. E.g.,

class dog {
  public: 
    string name;
    string color;
    float weight;
};

dog fido{"Fido", "brown", 37.6};

fido.name   // evaluates to "Fido"
fido.color  // evaluates to "brown"
fido.weight // 37.6

If we create more than one dog, they are all independent:

dog fido{"Fido", "brown", 37.6};
dog grimm{"Grimm", "white spots", 30};

grimm.color // white spots
fido.color  // brown
class owner {
  public:
    string name;
    vector<dog> dogs;
};

owner jsmith;
jsmith.name = "John Smith";
jsmith.dogs.push_back(fido);
jsmith.dogs.push_back(grimm);

jsmith.dogs.at(0).name // "Fido"

We can create an “anonymous” dog that doesn’t live in a box:

fido = dog{"Not Fido", "Green", 1000};

Besides having data attached to them, we can add behaviors, by adding methods (sometimes methods are called “member functions”, to correspond to “data members”).

class dog {
  public:
    void speak() {
        cout << "Woof!" << endl;
    }
};

dog fido;
fido.speak(); 

A member function can refer to the data members of the current object. E.g., speak() can use name, color, etc. and they will implicitly refer to whatever dog is speaking.

class dog {
    ...
    void speak() {
        cout << "Woof! says " << name << endl;
    }
}

fido.speak();  // Prints "Woof! says Fido"
grimm.speak(); // Prints "Woof! says Grimm"

Methods can be defined inside the class, as above, or we can declare the method inside the class, and then put the method implementation elsewhere:

class dog {
    void speak(); // Declaration
}

// Method implementation
void dog::speak() {
    ...
}

Constructors

An object can control how it is constructed. By default, every class gets a constructor that takes all values for all the members, as we did above by giving the name/color/weight for each dog.

class dog {
  public:
    dog(string n) {
        name = n;
        color = "Unknown";
        weight = -1;
    }
};

Now we can construct dogs from just a name:

dog fido{"Fido"};

This fills in the other members for us. Note that we lose the ability to construct a dog from all three members. If we want it back, we have to write a constructor to do it:

class dog {
  public:
    dog(string n) {
        name = n;
        color = "Unknown";
        weight = -1;
    }

    dog(string n, string c, float w) {
        name = n;
        color = c;
        weight = w;
    }
};

A constructor like the first one that takes a single argument also defines an implicit conversion from string to dogs:

dog fluffy = "Fluffy";

If you don’t want this behavior, you can label the constructor explicit

class dog {
  public:
    explicit dog(string n) {
        name = n;
        color = "Unknown";
        weight = -1;
    }

    ...
};

Access specifiers

The public at the top is an access specifier. It determines whether things outside the class can access parts of the class public says “anyone can access this”. We can also say private: which means, “only the class itself can access this”.

Usually, we try to make the data members of a class private, and the methods public, allowing the public methods to control and moderate access to the private members.

class thing {
  public:
    int x;
    float y;
    void z() { cout << "Hello!"; }

  private:
    int a;
    float b;
    void c() { cout << "Secret!"; }
};

thing q;
q.x   // OK
q.y   // OK
q.z() // OK
q.a   // ERROR
q.b   // ERROR
q.c() // ERROR

A class can have friend functions, which are functions outside the class, but which are still granted access to the class’s private elements:

class thing {
    ...
    friend f();
};

void f() {
  cout << q.a << q.b << q.c() << endl; // All OK
}

Why would we sometimes write a class operation as a function (friend or not) rather than a method?

Multi-file projects

Source files end with .cpp, header files end with .h. Header files contain function declarations, class definitions. Source files contain method definitions, class definitions (but they will only be visible in that file) and function definitions (function declarations, but again, only visible within the file). Basically, you put things in a .h to make them visible to other parts of the program that want to use them.

If other parts of the program want to use something, they #include the appropriate .h file (containing the declarations/definitions for whatever we want to use).

When we compile a project consisting of multiple source files, we have to tell the compiler about all the files:

compile file1.cc file2.cc ...

Header files normally contain include guards to prevent them from being included more than once:

file.h

#pragma once

#ifndef FILE_H_
#define FILE_H_

// Stuff goes here

#endif

Note that you only need to put stuff into an include file that you want to be visible to other parts of your program. If a file has functions, or even classes, that are not needed in the rest of the program, those can stay in the source file.

You can put a class definition in a header file, with just the method declarations in the class, and then put the method definitions in the corresponding source file.

Header files can (and often should) include other header files. Source files should almost never include other source files, only header files.

Exceptions and assertions

Exceptions are how C++ signals that something has gone wrong, but you might be able to fix it.

We have to tell C++ that we are interested in exceptions:

try {
    // do stuff
}
catch( ) {
    // Handle exception
}

The catch() part has to specify what kind of exception we are interested in catching. E.g., to catch any of the standard exceptions:

try {

}
catch(exception& e) {

}

(We always catch exceptions by reference.)

Standard exception hierarchy

exception
|
+---- logic_error
|     +-------------- invalid_argument
|     +-------------- domain_error
|     +-------------- invalid_argument
|     +-------------- length_error
|     +-------------- out_of_range
|
+---- runtime_error
|     +-------------- range_error
|     +-------------- overflow_error
|     +-------------- underflow_error
|     +-------------- system_error
|                     +--------------- ios_base::failure
+---- bad_alloc
      +-------------- bad_array_new_length

We can catch more than one type of exception, but we have to list them in order from most to least specific:

try {

}
catch(range_error& e) {

}
catch(runtime_error& e) {

}
catch(exception& e) {

}

If you want to catch any kind of exception, even those not part of the standard hierarchy, you can write

try {

}
catch(...) {

}

Example:

int main() {
    try {
        cout << "Start"  << endl;
        f(); g();
    }
    catch(runtime_error& e) {
        cout << "RE" << endl;
    }
    catch(out_of_range& e) {
        cout << "OOR" << endl;
    }

    return 0;
}

void f() {
    try {
        g();
        throw system_error{};
    }
    catch(logic_error& e) {
        cout << "LE" << endl;
    }
}

void g() {
    cout << "In g" << endl;
    throw out_of_range{};
}

Assertions – use these for when things go wrong internally, so that the only thing to do is crash the program as soon as possible.

#include <cassert>

assert(x > 0); // Crash if x <= 0

File access

fstream f{"my-file.txt"};

f << "Hello"; // Write to file

int x;
f >> x;  // Read from file

Overloading <<:

class thing {
  public:
    float x,y;
};

ostream& operator<< (ostream& out, thing t) {
    out << x << " " << y;
    return out;
}

istream& operator>> (istream& in, thing& t) {
    float x, y;
    if(in >> x) 
        if(in.peek() == ' ') {
            in.ignore(1);
            if(in >> y) {
                t.x = x;
                t.y = y;
            }
        }
}