C++

This lab is a quick introduction to C++ programming. During the course of the lab, you will implement a class to represent a polynomial. The lab will briefly introduce the following topics:

Resources

Files

The files for this lab are found in CMSC12300 GitHub repository under cmsc12300/labs/lab4/. Please run git pull origin master in your local copy of the repository to fetch the files.

Introduction

As the name suggests, C++ is an extension of C, meaning it adds functionality on top of C. The language is in fact a superset of C, meaning that everything you already know about C, you can use inside your C++ program. However, in many cases there will be a more C++ way of doing things, as we shall see a few examples of in this lab.

C++ adds many features to C, most notably classes, giving you the choice to program object-oriented, just like in Java. Another feature in C++ that you might recognize from Java is generic programming using templates, which in Java allowed you to use List<String> to mean a list of strings. Finally, C++'s Standard Template Library (STL) includes plenty of useful objects and containers, such as strings, dynamic arrays, hash maps, queues, etc.

Compiling

The familiar GCC compiler can compile C++ and offers a shorthand for doing so using the command g++ instead of gcc. In fact, that is all we need to change:

$ g++ source1.cpp source2.cpp
$ ./a.out

We use the extension cpp now to mark that we're using C++. For header files, many people still use a single h, but some people prefer to be more clear and use hpp.

Printing

Printing to standard output in C is done through printf, which is still possible under C++. However, a more C++ way is to use input/output streams from the STL:

#include <iostream>

int main() {
    std::cout << "Hello world" << std::endl;
    return 0;
}

The std:: accesses cout and endl (newline) inside the namespace std, which is where everything in the STL can be found. To avoid this verbosity, we can place everything in the STL in the global namespace:

#include <iostream>
using namespace std;

int main() {
    cout << "Hello world" << endl;
    return 0;
}

In Python, you learned that doing from numpy import * is frowned upon, which is similar to what we're doing here. However, in C++, this is an accepted style.

Classes

In this lab, you will be constructing a class to represent a polynomial. We will limit ourselves to second-order polynomials or lower to make it easier:

\begin{equation*} ax^2 + bx + c \end{equation*}

Take a look in poly.h, where you will find all members and prototypes declared already. You will not need to change this file in this lab.

It is considered bad style to use using namespace in a header file, since it pollutes the global namespace of any cpp file that includes it. This is why we write std::ostream in full at one point in the file.

The file poly.cpp does not implement all functions listed, which is what you will do as part of the lab. We have also povided you with a main.cpp that runs some tests, to see if the class works correctly.

Prototypes and implementation

Take a look at poly.cpp and you should get a feel for how prototypes of member functions are implemented by prefixing them with Polynomial::.

Exercises

  • Create empty functions in poly.cpp for all function prototypes found in poly.h, so that your can successfully compile and run g++ poly.cpp main.cpp. If the function requires a return value, return an arbitrary value for now. If the return type is Polynomial, you can temporarily return Polynomial().

    In the rest of the lab, you should compile and run your program often.

Constructors and destructors

The function prototypes that are called Polynomial and ~Polynomial are the constructor and the destructor, respectively. You can have several distinct constructors, as long as their function signatures are different. The constructor is the entry point of any class instance, while the destructor is the exit point. For instance, if we allocate memory in the constructor, we should free it in the destructor. If you remember the node class that we worked with, we had the functions mkNode and freeNode. These are now replaced by a constructor and a destructor.

Exercises

  • Increment and decrement numPolynomials by 1 in the constructors and destructor, respectively. This is a static variable that is shared between all instances, keeping track of how many polynomials have been created and are still in memory. Make sure to run the tests to see that this works.
  • Implement the copy constructor. Mimic the other constructors, but take the initial values from another Polynomial instance. Notice that we do not use the return keyword in constructors, even though we might be tempted to so conceptually. The copy constructor test won't pass until you have implemented the == operator later.

References

In the file, some of the parameters are:

const Polynomial &p

The const denotes that the compiler won't allow us to change the object, but what does the & mean? It denotes a reference, and is similar in theory to a pointer but with more type safety, since it can't take the value NULL. It acts just like a stack object, so we do not need to dereference it or use the arrow operator (->) to access its members. Notice that this operator is not the same as the reference operator &, that finds the pointer of an object; the compiler knows which one you refer to by context.

So, why are we passing variables as const Polynomial & instead of simply as Polynomial? This is a common practice in C++, since the former avoids the overhead of creating a copy of the object, which would happen in the latter case. Passing as a pointer, as we did in C, has the same memory benefits, but in that case we can't guarantee that we are given a valid non-null object.

In summary, passing objects as references instad of pointers offers the guarantee that the object is valid, without requiring expensive copies to be made. Note that references can only replace pointers in some situations, such as passing parameters. We will still need to use pointers when managing dynamically allocated memory.

Instances

As with any basic data types in C, we can create class instances both on the stack:

Polynomial p(1, 2, 3);

or dynamically on the heap:

Polynomial *p = new Polynomial(1, 2, 3);

// ...

delete p;

In C++, instead of malloc and free, it is more common to use new and delete, which not only allocates and frees memory for us, but also ensures that the constructor and destructor are called.

Operator overloading

We have seen many similarities to Java so far, but overloading operators is something that is forbidden in Java, but allowed in C++.

Exercises

  • Implement the assignment operator =. It should set all the member variables to p, and then return a reference to itself (this part has already been done for you). The operator returns itself to allow expressions such as obj1 = obj2 = obj3;.
  • Implement the + operator. This does not return a reference, since it should create a new instance instead. This operator should not change the instance variables, in fact, since it's decorated with const the compiler won't allow you even if you try.
  • Implement the == operator, and require all cofficients to be equal for the objects to be equal.

At this point you should pass at least the first four tests.

Note

Operators do not work on pointers of objects. If we have two pointers of polynomials:

Polynomial *p1 = new Polynomial(1, 2, 3), *p2 = new Polynomial(1, 2, 3);

Then p1 == p2 will return false, since we're comparing the pointers and thus memory addresses. Only when we do *p1 == *p2 will we use the operator that we just overloaded and have it return true.

Member functions

Just like in Java, we use this to refer to the current instance inside a member function. We can omit this when accessing member variables, unless there is a name collision. Notice that this refers to a pointer of the object, and to access the object itself we must dereference it as *this.

Exercises

  • Implement evaluate, which returns the value of the polynomial, given a certain x.
  • Implement hasRealSolution, getRealSolution1 and getRealSolution2. These refer to setting the quadratic expression to 0 and trying to solve it. Let getRealSolution1() >= getRealSolution2().

Printing the object

As we saw above, printing in C++ is done by linking objects using the << operator, starting with the cout object. To make our class fit into this framework, we have to overload the operator that takes a cout object (which is an ostream object) on the left, and our object on the right. The syntax for this has already been included, as well as a basic implementation. Since this operator is not a member function of Polynomial, we have to declare it a friend of the class, in order to give it priveleges to access the private coefficients.

Exercises

  • Right now, it does not print the expressions in a very natural way, as can be seen if running main.cpp. Negative values should not be displayed as plus and then minus. Coefficients with value 0 can be omitted altogether and a 1 in front of x or x^2 is unnecessary.

Bonus

Here are some exercises if you still have time:

Exercises