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 ofusing 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
fromMy 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() {
("%d %f", abs_val(-23), abs_val(-20.3f));
printf}
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) {
= 0;
txt += 1;
txt }
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 |
---|---|
|
|
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
and an operator malloc
1 and then invoke a constructordelete
that acts like invoke a
destructor and then
,free
Thus, we could test the code above with
*a = new gleam();
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 new
ed,
and are typically omitted if there is no need to do that.
class gleam2 {
int a;
int b;
public:
int inOrder();
(int x, int y) { this->a = x; this->b = y; }
gleam2~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 |
---|---|
|
|
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() {
<< "Hello, world!" << endl;
cout }
or
#include <iostream>
using namespace std;
int main() {
<< "Hello, world!" << endl;
cout }
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 ostream
s 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
functions,
which are used to add functions and operators to existing classes in new
files. A common example is to add friend
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, andstack<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 withcout << my_stack;
instead.
- If you have time, implement
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)
>>
astring
fromcin
make a new
stringstream
and<<
astring
into it>>
adouble
from thestringstream
if that
>>
ing into thestringstream
causes thestringstream
to.fail()
,check if the
string
is==
to an operator, andif 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 pop
ing 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 pop
ing
s
does not pop
the stack that was passed in,
only the local copy of it that the operator<<
function contains.
C++ generally manages the heap slightly differently when using
malloc
vsnew
, so neverfree
something allocated withnew
or vice verse.↩︎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.↩︎