CS253: Software Development with C++

Spring 2021

Struct And Class

Show Lecture.StructAndClass as a slide show.

CS253 Struct And Class

made at imgflip.com

struct

struct Point {
    int x, y;
};
Point p;
cout << p.x << '\n';
p.x = 8;
p.y = 9;
cout << p.x << ',' << p.y << '\n';
c.cc:5: warning: 'p.main()::Point::x' is used uninitialized in this function
0
8,9

class

class Point {
  public:
    Point(int a, int b) { x = a; y = b; }   // constructor (alias ctor)
    int get_x() const { return x; }         // accessor
    int get_y() const { return y; }         // accessor
    void go_right() { x++; }                // mutator
  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 rarely 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
    int get_y() const { return y; }         // accessor
    void go_right() { x++; }                // mutator
  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”!

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

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-NOk7ye

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-lZAqdM

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