CS253: Software Development with C++

Fall 2021

Struct And Class

Show Lecture.StructAndClass as a slide show.

CS253 Struct And Class

made at imgflip.com

struct

struct Point {
    int x[100], y;
};
Point p;
p.x[90] = 12345;
p.y = 67890;
cout << p.x[90] << ',' << p.y << '\n';
12345,67890

class

class Point {
  public:
    Point(int a, int b) { x = a; y = b; }   // constructor (alias ctor)
    int get_x() const { return x; }         // accessor (getter)
    int get_y() const { return y; }         // accessor (getter)
    void go_right() { x++; }                // mutator (setter)
  private:
    int x, y;                               // Hands off!
};

this

The special variable this is a pointer (not a reference, as in Java) to the current object. It’s not often needed. Here is an unnecessary use of this:

class Point {
  public:
    Point(int a, int b) { x = a; y = b; }
    double radius() const { return sqrt(x*x + y*this->y); }
  private:
    int x, y;
};
Point p(300,400);
cout << p.radius() << '\n';
500

Inside Point::radius(), this is of type const Point *, because the method is const.

Output

To make a class work for output, define operator<< appropriately:

class Point {
  public:
    Point(int a, int b) { x = a; y = b; }   // ctor
    int get_x() const { return x; }         // accessor (getter)
    int get_y() const { return y; }         // accessor (getter)
    void go_right() { x++; }                // mutator (setter)
  private:                                  // Mine!
    int x, y;
};

ostream &operator<<(ostream &out, const Point &p) {
    return out << p.get_x() << ',' << p.get_y();
}

int main() {
    Point p(12, 34);
    cout << p << '\n';      // invoke operator<<
}
12,34

Example

class Quoted {
    string s;
  public:
    Quoted(const string &word) : s(word) { }
    string get() const { return "“" + s + "”"; }
};

ostream &operator<<(ostream &os,
                    const Quoted &rhs) {
    return os << rhs.get();
}

int main() {
    Quoted name("Bob");
    cout << "Hi, " << name << "!\n";
}
Hi, “Bob”!

mutable

mutable is a hack. It indicates that, even if a method is const, and hence should not modify data members, it’s ok to modify this data member. mutable is used when you want to change a data member without really changing the state of the object: caching a computed value, counting instances, etc.

mutable #1

class Process {
  public:
    pid_t get_pid() const {
        return getpid(); // assume this is slow
    }
};

Process p;
cout << p.get_pid() << ' ' << p.get_pid() << '\n';
1748869 1748869

This is the straightforward solution. However, if getpid() is slow, then we’re calling the slow function twice. The process id (pid) doesn’t change! We should only call getpid() once.

mutable #2

class Process {
    const pid_t pid = getpid();
  public:
    pid_t get_pid() const {
        return pid;
    }
};

Process p;
cout << p.get_pid() << ' ' << p.get_pid() << '\n';
1748870 1748870

That works, but always calls getpid(). What if we never need the process id? We called getpid() for no reason!

mutable #3

class Process {
    mutable pid_t pid = -1;
  public:
    pid_t get_pid() const {
        if (pid == -1)
            pid = getpid(); // assume this is slow
        return pid;
    }
};

Process p;
cout << p.get_pid() << ' ' << p.get_pid() << '\n';
1748871 1748871

This is the solution. getpid() is called once, at most, and not called at all if the process ID is never required. (Arguably, a local static variable inside the method would be simpler.)

methods: in-line and not

Methods are declared in the class, but can be defined inside or outside of the class.

class Quoted {
    string s;
  public:
    Quoted(const string &word) : s(word) { }
    string get() const; // declaration only
};
string Quoted::get() const {
    return "“" + s + "”";
}
ostream &operator<<(ostream &os, const Quoted &rhs) {
    return os << rhs.get();
}

int main() {
    Quoted name("Slartibartfast");
    cout << "I am " << name << ".\n";
}
I am “Slartibartfast”.

Protection

Protection

class Foo {     // A class, with a variable of each type
  public:
    int pub;    // I’m public!
  private:
    int priv;   // I’m private!
  protected:
    int prot;   // I’m a little teapot, short & stout.
};

int main() {
    Foo f;

    f.pub = 1;          // great
    // f.priv = 2;      // nope
    // f.prot = 2;      // nope

    return f.pub;
}

Friends

Friend Declarations

One class can declare another class/method/function to be its friend:

class Foo {
    friend class Bar;
    friend double Zip::zulu(int) const;
    friend int alpha();
    friend std::ostream &operator<<(std::ostream &os, const Foo &);
  private:
    int x,y;
};

Restrictions

Don’t go nuts

Counting

Try #1

class Foo {
  public:
    Foo() { counter++; }
    ~Foo() { counter--; }
    int get_count() const { return counter; }
  private:
    int counter=0;
};

int main() {
    Foo a, b, c, d, e;
    cout << e.get_count() << '\n';
}
1
Why doesn’t this work?

Each instance of Foo has its own counter, and each one gets initialized to zero, then incremented to one.

Try #2

int counter=0;

class Foo {
  public:
    Foo() { counter++; }
    ~Foo() { counter--; }
    int get_count() const { return counter; }
};

int main() {
    Foo a, b, c, d, e;
    cout << e.get_count() << '\n';
}
5

That works, but it has an evil global variable. 👹

Try #3

class Foo {
  public:
    Foo() { counter++; }
    ~Foo() { counter--; }
    int get_count() const { return counter; }
  private:
    static int counter=0;
};

int main() {
    Foo a, b, c, d, e;
    cout << e.get_count() << '\n';
}
c.cc:7: error: ISO C++ forbids in-class initialization of non-const static 
   member 'Foo::counter'

That failed.

Try #4

class Foo {
  public:
    Foo() { counter++; }
    ~Foo() { counter--; }
    int get_count() const { return counter; }
  private:
    static int counter;
};
int Foo::counter = 0;

int main() {
    Foo a, b, c, d, e;
    cout << e.get_count() << '\n';
}
5

There we go! Only a single, shared, counter, with class scope.

static class variables

A static member variable:

static methods

A static method:

const methods

class Ratio {
    int top, bottom;
  public:
    Ratio(int a, int b) : top(a), bottom(b) { }
    double get_real() {
        return top / double(bottom);
    }
};

int main() {
    const Ratio f(355,113);
    cout << f.get_real() << '\n';
}
c.cc:12: error: passing 'const Ratio' as 'this' argument discards qualifiers

Oops—can’t call a non-const method on a const object.

const methods

class Ratio {
    int top, bottom;
  public:
    Ratio(int a, int b) : top(a), bottom(b) { }
    double get_real() const {
        return top / double(bottom);
    }
};

int main() {
    const Ratio f(355,113);
    cout << f.get_real() << '\n';
}
3.14159

Making Ratio::get_real() const fixed it. It also properly indicates that get_real() doesn’t change the object.

Rationale

const methods

class Ratio {
    int top, bottom;
  public:
    Ratio(int a, int b) : top(a), bottom(b) { }
    double get_real() const {
        return top / double(bottom);
    }
};

void show_ratio(Ratio r) {
    cout << r.get_real() << '\n';
}

int main() {
    Ratio f(355,113);
    show_ratio(f);
}
3.14159

That call to show_ratio copies an object by value. Expensive! 😱 Well, OK, it’s only two ints—big deal. If the object had a lot of data, or used dynamic memory, then copying would be expensive.

const methods

class Ratio {
    int top, bottom;
  public:
    Ratio(int a, int b) : top(a), bottom(b) { }
    double get_real() const {
        return top / double(bottom);
    }
};

void show_ratio(const Ratio &r) {
    cout << r.get_real() << '\n';
}

int main() {
    Ratio f(355,113);
    show_ratio(f);
}
3.14159

Look! A const Ratio! r is now const, so get_real() must be const.

Implementation

The compiler’s implementation of a const method is simple; it regards the hidden implicit pointer argument this as pointing to a const object, which explains the message:

class Foo {
  public:
    void zip() {}
};

int main() {
    const Foo f;
    f.zip();
}
c.cc:8: error: passing 'const Foo' as 'this' argument discards qualifiers

In Foo::zip(), this is type Foo *, which can’t point to const Foo f.

Poor Strategy

Good Strategy

constexpr variable

constexpr methods

class Foo {
  public:
    constexpr int bar() { return 42; }
};

Foo f;
cout << f.bar();
42

constexpr methods

class Foo {
  public:
    constexpr int bar() { return getpid(); }
};

Foo f;
cout << f.bar();
c.cc:3: error: call to non-'constexpr' function '__pid_t getpid()'

Temporary files

Conversion methods

class Tempfile {
  public:
    Tempfile() { close(mkstemp(name)); }
    ~Tempfile() { remove(name); }
    string tempname() const { return name; }
  private:
    char name[18] = "/tmp/cs253-XXXXXX";
};

Tempfile t;
const string fname = t.tempname();
cout << fname << '\n';
/tmp/cs253-otSuy9

Conversion methods

Let’s add an operator string method:

class Tempfile {
  public:
    Tempfile() { close(mkstemp(name)); }
    ~Tempfile() { remove(name); }
    operator string() const { return name; }
  private:
    char name[18] = "/tmp/cs253-XXXXXX";
};

Tempfile t;
const string fname = t;
cout << fname << '\n';
/tmp/cs253-qsNoyb

Just treat it like a std::string, and it works!