Changelog:

  • 19 Apr 2023: correct link to postfix assignment
  • 21 Apr 2023: tone down using namespace std suggestion since that (in my opinion) is very bad style; add mention of using std::foo;
  • 21 Apr 2023: adjust note about the term STL
  • 26 Apr 2023: remove nongramatical due-to-edits text that was meant to regard using namespace std from My Task
  • 26 Apr 2023: make recommendatoin re: stringstream explicitly suggest making a new one each time, or alternately giving reset instructions

The goal of this lab is to explore how C++ adds minimal extensions to C in order to provide several features that have become common in object-oriented languages. The subsequent lab will have you use these features to work with some basic C++ code.

1 How this lab works

This lab introduces key features C++, and describes how it deffers from C. Once you’ve completed the reading, we ask you implement a postfix calculator using C++. If you already know C++, skip to end of lab and start coding.

1.1 Compiling

Many parts of this writeup will ask you to inspect various aspects of compiled C++ code. You can run a C++ compiler by using clang++ (or g++); to access the resulting assembly use the -S flag (e.g., clang++ -S ex.cpp generates assembly in ex.s); to inspect the data set by the code use lldb (or gdb).

You’ll likely find that -O1 will generally create much shorter code without removing useless code that you might have wanted to test something.

You should always give your C++ files the extension .cpp. Some

2 Function name overloading

In C, if you name a function baz then it is compiled with label baz. That means you cannot re-use the name baz for more than one function.

In C++, function names are only part of the compiled label: argument types and where the name appeared are also part of it. This allows code like

int abs_val(int x) { return x < 0 ? -x : x; }
double abs_val(double x) { return x < 0 ? -x : x; }
int main() {
    printf("%d %f", abs_val(-23), abs_val(-20.3f));
}

to be compiled; the compiler picks which version of abs_val to invoked in each instance using information derived during type checking.

This renaming of functions to arrive at labels is called name mangling.

3 Namespaces

In C, every function and global variable’s name must be unique among all the files you link with. When you write all your own code, that’s not too hard to do; but when you are using third-party libraries there is no reason to expect them not to conflict in names. Because of this, major C libraries generally pick some kind of prefix to all function names, but it can be annoying to read and write code when virtually every function starts with the same prefix.

Later languages have added in a full module or package system, with full scoping and so on, but C++ uses a simpler mechanism called namespaces. These are included in the name mangling to further decrease the chance of name collisions.

Name mangling for namespaces uses a length-before-name technique, so that namespace welcome would become 7welcome in the mangled name. This is important to be able to have numbers in the namespace hierarchy: 7welcome2it represents the two nested namespaces welcome::it while 10welcome2it is the single namespace welcome2it

4 Operator overloading

C++ allows you to redefine what almost every operator does. You can define functions whose name is operator+, operator<<=, etc, and they will be used in place of built-in behavior for +, <<=, etc, when those operators are applied to the appropriate types.

5 Other odds and ends

C++ compilation also makes use of a few other syntactic sugar pieces to help make code easier to write. One example is the pass by reference syntax, a short-hand way of using pointers. For example, what in C might be

char *strsep_c(char **txt, char delim) {
    if (!txt || !*txt) return 0;
    char *ans = *txt;
    while(**txt && **txt != delim) *txt += 1;
    if (**txt == delim) {
        *txt = 0;
        *txt += 1;
    }
    return ans;
}

can be written in C++ as

char *strsep_c(char *& txt, char delim) {
    if (!txt) return 0;
    char *ans = txt;
    while(*txt && *txt != delim) txt += 1;
    if (*txt == delim) {
        txt = 0;
        txt += 1;
    }
    return ans;
}

These two compile to virtually the assembly except that the compiler takes care of ensuring that the second, which uses pass-by-reference syntax, is always given a valid reference so the two-part check in the first is just a 1-part check in the second

Recent versions of C++ have added many other similar tricks for efficient coding with more compile-time checking, such as more careful casts, self-deallocating pointers, etc.

6 Classes

C++ is often characterized as C supporting objected oriented coding. This section explores several of those concepts:

C++ adds a class keyword that is identical to struct in almost every way, except a class defaults to private: members and a struct to public:. C++ also removes the need to typedef to get a simple name for a struct or class.

6.1 Member functions

C++ also extends struct and class to allow them to declare member functions as well as member attributes:

.hpp header .cpp implementation
class gleam {
    int a;
    int b;
public:
    int inOrder();
};
int gleam::inOrder() {
    return this->a <= this->b;
}

6.2 constructors, destructors, new and delete

Constructors are an important part of how OO objects are initialized. C++ has added an operator new that acts like malloc1 and then invoke a constructor and an operator delete that acts like invoke a destructor and then free,

Thus, we could test the code above with

gleam *a = new gleam();
int b = a->inOrder();

To add a constructor, make a member function with the same name as the class; a destructor’s name is preceded by a tilde ~. Neither should have a return type specified. Destructors are usually used to delete anything the constructor newed, and are typically omitted if there is no need to do that.

class gleam2 {     
    int a;        
    int b;        
public:           
    int inOrder();
    gleam2(int x, int y) { this->a = x; this->b = y; }
    ~gleam2() { puts("Gone"); }
};

6.3 Inheritance and overloading

We’ll talk about how C++ implements inheritance and overloading in lecture.

6.4 Templates

C++ also has a template language, which looks superficially like Java’s generic syntax but is actually a full programming language in its own right. Most casual C++ programmers I know only use the basic type-generic aspect of templates, not any of their advanced features.

6.5 Reference arguments

In C, if you have an argument of type int * you don’t know if it is an array or just a value you are supposed to set. C++ adds syntax to allow these to look different the reference argument type.

If you write a function where an argument name is followed by an ampersand (e.g., int &x) then C++ will compile it to be a pointer, but have your syntax look like it is a variable.

The following two pieces of code are equivalent in how they run

C code C++ code
int f(int *x) {
    int ans = *x + 2;
    *x = 3;
    return ans;
}
int f(int &x) {
    int ans = x + 2
    x = 3;
    return ans;
}

Both pieces of code turn into equivalent assembly, but the C++ version uses type-checking to ensure that x is never treated as if it were an array instead of a single reference.

6.6 Templates

A template in C++ is an outline of how to create code if it is needed. Templates uses angle-brackets, like generics in Java. Java, however, discards the generics during compilation as part of the type checking step. C++ instead uses them to generate special code for each template argument created. Thus, C++ code like

template <typename T> T foo(T x) { /* ... */ }

does not create any code, by itself. Instead, each time you use the template with different template parameters the compiler creates code for that parameterization. Thus, if you later have

int x = foo(3);
double y = foo(3.14);

the compiler will create two different foo functions: one that has an int parameter and return type and another that has double.

Note that the compiler will try to guess what template parameters were missing; if you want to be explicit, you can also write

int x0 = foo(3); // compiler figures out it is a template, and deduces type
int x1 = foo<>(3); // told it is a template, compiler deduces type
int x2 = foo<int>(3); // told it is a template and type

Another difference from Java is that the parameters of a template do not need to be type names; they could also be values, like integers. Value templates are unusual enough in C++ we’ll say no more about them here.

C++ template metaprogramming is a complicated topic on which entire books have been written. Some libraries, such as boost and FFTW are well-known for complicated template usage.

6.7 Operator Overloading and Friends

C++ recognizes that there is no magic to operators, and it is awkward to write your matrix class with x.add(y) when you write your numbers as x + y instead, so it lets you overload operators. In essence, this lets you add, to any class you want to work with, functions that are the implementation of operators. This lets you write math with your custom matrix class just like you do with the built-in number types.

Once you have this, though, it becomes tempting to use in other ways. One of the most famous is the ostream type, C’s wrapper over file writing, which overloads the left-shift operator to mean write.

The following C++ code writes Hello, world! and then an end-of-line. There are no integers being shifted; << means left-shift for integers, but it means here’s some output with ostreams like cout

#include <iostream>

int main() {
    std::cout << "Hello, world!" <<  std::endl;
}

This code also uses a namespace; namespaces are a way of avoiding name collisions, and most of the common C++ libraries we’ll use are in the std namespace. We could have equivalently written it as

#include <iostream>
using std::cout;
using std::endl;

int main() {
    cout << "Hello, world!" << endl;
}

or

#include <iostream>
using namespace std;

int main() {
    cout << "Hello, world!" << endl;
}

Because << is also overloaded to work with multiple types, we can write

#include <iostream>
int main() {
    std::cout << "Hello, all " << (3 + 1) << " dimensions!" << std::endl;
}

cout, by the way, is C++’s ostream wrapper around file descriptor 1, just as stdout is C’s FILE * wrapper around the same.

Not all ostreams will flush output as promptly as others. For debugging, you should definitely use cerr instead of cout as it is better about showing everything when you ask it to be shown instead of delaying until later.

C++ also allows what it calls friend functions, which are used to add functions and operators to existing classes in new files. A common example is to add ostream << mytype operators for new types.

7 STL and standard library

The C++ Standard Library has a variety of types and algorithms in it. Some of the best known of these are part of the Standard Template Library (STL)2, and sometimes people use STL informally to mean the full C++ Standard Lbirary. There is more in the library than we can fully cover, but the following will get you going:

include provides STL? description
<list> std::list<T> STL doubly-linked list; key functions push_/pop_front, push_/pop_back
<map> std::map<K,V> STL tree-map; key function [key]
<stack> std::stack<T> STL stack; key functions push, pop, top
<queue> std::queue<T> STL queue; key functions push pop, front
<vector> std::vector<T> STL resizable array; key functions [index], insert, push_/pop_back
<iterator> std::iterator<T> STL use as for(it = c.begin(); it != c.end(); ++it) for STL iterator it and collection c
<string> std::string Wraps a const char * with various useful methods (find, replace, size, +=, ==, etc)
<iostream> std::istream and std:ostream C++ file handling; operator << writes and >> reads; defines cin, cout, and cerr
<sstream> std::stringstream Lets you treat a string like a file

There are many others not listed above; see wikipedia and/or the excellent reference cppreference.com for a reasonable overview.

Note that many C++ types overload == to compare values the way that .equals() does in Java. Hence "Hi" == "Hi" compares pointers, but std::string("Hi") == std::string("Hi") compares words.

8 Your Task

Implement a postfix calculator, like you did in CSO1 but this time use C++.

  • You must use cin to read input, >> to parse numbers, string and == to find operators, and stack<double> as your stack.

  • You must print your stack by repeated display-and-pop, as stack does not have printing capability.

    • If you have time, implement ostream & operator<<(ostream & o, stack<double> s) to display a stack, and end your program with cout << my_stack; instead.

We strongly recommend, but do not require, using a two-step read: read a word from cin into a string, then feed the string into a stringstream and use the stringstream to parse a number. However, there are other solutions and you do not have to do this if you do not want to.

When you are done either show your TA your code for a checkoff or submit it to the submission site.

The input processing can look like a loop which, as long as cin is .good() (i.e., not closed and with no read errors)

  1. >> a string from cin

  2. make a new stringstream and << a string into it

  3. >> a double from the stringstream

  4. if that >>ing into the stringstream causes the stringstream to .fail(),

    1. check if the string is == to an operator, and

      1. if so apply the operator (ending if there are fewer than 2 operands)

        otherwise, end the loop

      otherwise, push the double

I recommend making a new stringstream object each time. (Otherwise, you need to make sure you properly reset the state of the stringstream, such as by calling stream.str("") to clear the internal buffer and stream.clear() to reset the EOF/fail information.)

Use a std::stack<double> as your stack. Note that pop returns void; you’ll need to use top to retrieve a value before poping it off the stack.

Displaying a stack is not something the standard library natively supports, so you’ll need to do that manually.

If you have time, implement your own ostream & operator<<(ostream & o, stack<double> s) which repeatedly displays (with <<) the top() and then pop()s until the stack is .empty(). Note that we pass in the ouput stream by reference (with &), but pass the stack by value. Passing by value means passing a copy, so poping s does not pop the stack that was passed in, only the local copy of it that the operator<< function contains.


  1. C++ generally manages the heap slightly differently when using malloc vs new, so never free something allocated with new or vice verse.↩︎

  2. Historically, the Standard Template Library was distributed separately from the rest of the C++ standard library, which is why you’ll often see it distinguished from it.↩︎