A Primer on C++

The purpose of this document is to give you a quick primer on C++. If you're already familiar with C++, you can safely skim or skip this document.

We'll first cover CMake which is the build tool we will be using. We won't cover deeply on how to use it (since you won't have to) except for the basic commands to get you set up. We'll also go over the general project structure. We'll then cover structs and classes in C++. If you've programmed in Java, you might recall the notion of objects. Indeed, classes are one of the distinguishing features of C++. Finally, we'll cover some basic features of the standard library that may be helpful.

CMake

For all the projects in this class, we'll be using a build system called CMake. Essentially, CMake helps us manage our Make files, and our Make files help us compile our executables.

Executables are nearly always built in a directory called build from your project directory. Thus, from your project directory, you should run the following

cd build
cmake ..

At this point, CMake will start to generate Make files for you. Staying in the build directory, you can now compile the project by running make all. You can run other targets from here as well, most notable of which is make check, which will run your tests.

Structs and Classes

We'll introduce structs and classes in C++ by examples. Suppose we wish to create a class that represents a "person". Each person will have public first and last names. Each person will also have an embarrassing secret that they'll keep private.

We'll start off by declaring the following in a header file person.hpp

#pragma once

#include <string>

class User {
    public: 
        // Constructor
        User(std::string first_name, std::string last_name);

        // Data members
        std::string first_name;
        std::string last_name;

        // Functions
        std::string CalculateFullName();

    private:
        // Private members
        std::string embarrassing_secret;

        // Private functions
        std::string ConcatenateStrings(std::string s1, std::string s2)
}

You might notice that we've declared functions but haven't defined them. We'll define the implementations in a C++ file person.cxx. We strongly prefer you use the this-> notation for accessing member variables and methods.

#include "person.hpp"

User::User(std::string first_name, std::string last_name, std::string secret) {
    this->first_name = first_name;
    this->last_name  = last_name;
    this->embarrassing_secret = secret;
}

std::string User::CalculateFullName() {
    return this->ConcatenateStrings(this->first_name, this->last_name);
} 

std::string User::ConcatenateStrings(std::string s1, std::string s2) {
    return (s1 + s2);
}

Finally, we can run in a main.cxx file

#include "person.hpp"

#include <iostream>

int main() {
    User crypto_alice = User("Alice", "the Allstar", "");
    std::cout << crypto_alice.CalculateFullName() << std::endl;
}

A struct is the same as a class in C++ except that its default visiblity is public. In this course, we'll often use structs to group together related data and classes to encapsulate bigger objects. We give an example of a struct below:

// Struct declaration
struct Coordinate2D {
    double x;
    double y;
    double AddCoordinates();
}

// Defining the function
double Coordinate2D::AddCoordinates() { 
    return (x + y) 
};

// Some ways of initializing a struct
Coordinate2D origin = {0, 0};
Coordinate2D cs1515;
cs1515.x = 15;
cs1515.y = 15;

The standard library

The standard library in C++ is rich in features. We'll go over and give examples of the most common features.

Input and Output Streams

In this section we'll give an example of the input and output streams std::cin and std::cout. These streams are used for reading input and printing output, respectively. For example, we can write the following program to continuously get input from the user and print it back to them:

#include <iostream>
#include <string>

int main() {
    while (true) {
        std::string input;
        std::cin >> input;
        std::cout << "You typed: " << input << "!" << std::endl;
    }
}

You'll notice we've included <string>. This is the header file for the standard library implementation of strings which we've implicitly been working with. We include header files when we want to import functionality from another file or library.

Vectors

Vectors in C++ are essentially dynamically sized arrays. We give the following example to illustrate important methods you may find helpful.

#include <vector>

// Initialize an empty vector
std::vector<int> v1;
v1.push_back(1);
v1.push_back(2); // Now v1 looks like {1, 2}

std::cout << v1.size() << std::endl; // This will print out 2

std::vector<int> v2 = {3, 4};

// Insert all of v2 onto the end of v1
v1.insert(v1.end(), v2.begin(), v2.end()); 

std::cout << v1 << std::endl; // Now v1 looks like {1, 2, 3, 4}

Pairs and Tuples

Often times we'll want to return a pair of tuple of values. In C++, the std::pair and std::tuple can help us with this.

#include <utility>  // For pairs
#include <tuple>    // For tuples
#include <string>   // For strings

std::pair<int, std::string> my_pair = std::make_pair(1, "second");
std::cout << simple_pair.first << std::endl; // Prints 1
std::cout << simple_pair.second << std::endl; // Prints "second"

std::tuple<int, double, std::string> my_tuple = std::make_tuple(15, 3.14, "third");
std::cout << std::get<0>(my_tuple) << std::endl; // Prints 15
std::cout << std::get<1>(my_tuple) << std::endl; // Prints 3.14 
std::cout << std::get<2>(my_tuple) << std::endl; // Prints "third"

Shared and Unique Pointers

Every object in your program is stored somewhere in memory, and that memory location has an address. We call a variable that holds a memory location a pointer. In C++, there are also "smart pointers" that are wrappers around raw pointers.

With smart pointers you still have your classic operators like * and ->, but in addition you get the benefit of lifetime management provided by the class. There are a couple of smart pointers, but the main ones to know of are std::unique_ptr and std::shared_ptr. Their uses are demonstrated below:

// Assume we have a class called "SomeClass" whose constructor takes in 2 arguments. 
std::unique_ptr<SomeClass> ptr = std::make_unique_ptr<SomeClass>(arg1, arg2);
std::shared_ptr<SomeClass> ptr = std::make_shared_ptr<SomeClass>(arg1, arg2);


A note on namespaces

Namespaces act as a "scope" on the names of functions and classes.

To see why they are useful, consider the fact that the standard library and the Boost library both have implementations of array-like classes and both call them array. Namespacing allows this to be possible by preventing name collisions i.e.

#include <array>
#include <boost/array.hpp>

std::array<int>     my_std_array;
boost::array<int>   my_boost_array;

More about our project structure

Our projects are all structures in a very standard way:

.
├── CMakeLists.txt
├── Doxyfile.in
├── format
├── build
│   └── test.sh
├── cmake
│   ├── Boost.cmake
│   ├── Cryptopp.cmake
│   ├── Curses.cmake
│   ├── Doctest.cmake
│   ├── Documentation.cmake
│   └── Warnings.cmake
├── include
│   ├── drivers
│   │   ├── cli_driver.hpp
│   │   ├── crypto_driver.hpp
│   │   └── network_driver.hpp
│   └── pkg
│       └── client.hpp
├── include-shared
│   ├── colors.hpp
│   ├── logger.hpp
│   ├── messages.hpp
│   └── util.hpp
├── src
│   ├── cmd
│   │   └── main.cxx
│   ├── drivers
│   │   ├── cli_driver.cxx
│   │   ├── crypto_driver.cxx
│   │   └── network_driver.cxx
│   └── pkg
│       └── client.cxx
├── src-shared
│   ├── logger.cxx
│   ├── messages.cxx
│   └── util.cxx
└── test
    ├── CMakeLists.txt
    ├── test.cxx

The files in the . and cmake directories manage the CMake project. The ./format script runs clang-format on your code; you should run this in your Docker container periodically. The test directory holds tests. The interesting folders are include, include-shared, src, and src-shared. We have a distinction between shared and non-shared libraries for the purposes of autograding. In general, the include folders hold header files, and the src folders hold code files. Each of the folders may be subdivides into further packages to allow ease of navigation. More on what each file does can be found in the project handouts.

More resources

There are many other interesting topics in C++ such as polymorphism, type deduduction, exception handling, etc. that we will not get into for this quick introduction. However, the following resources should be helpful if you are looking for more: