0%

C++ Review

C++ is a strongly typed programming language where every variable has a type, name, value, and location in memory.

1
int value = 42;

C++ types

the type of a variable defines the contents of the variable. Every type is either:

Primitive

  • int, stores integers
  • char, stores single characters/single byte
  • bool, stores a Boolean (true or false)
  • float, stores a floating point number
  • double, stores a double-precision floating point number
  • void, denotes the absence of a value

User-defined

  • Std
  • An unbounded number of user-defined types can exist – we’ll create many of our own!

C++ program

Every C++ program must contain a starting point. By the C++ standard, the starting point is a function:

1
int main();

By convention, the return value of main is 0 (zero) if the program was successful and non-zero on errors.

Classes

Encapsulation #1

C++ classes encapsulate data and associated functionality into an object.

Encapsulation

In C++, data and functionality are separated into two separate protections: public and private.

  • Public members can be accessed by client code.
  • Private members cannot be accessed by client code (only used within the class itself).

Encapsulation #2

In C++, the interface (.h file) to the class is defined separately from the implementation (.cpp file).

A header file (.h) defines the interface to the class, which includes: - Declaration of all member variables - Declaration of all member functions

An implementation file (.cpp) contains the code to implement the class (or other C++ code).

C++ standard library

The C++ standard library (std) provides a set of commonly used functionality and data structures to build upon.

Std Organization

The C++ standard library is organized into many separate sub-libraries that can be #include’d in any C++ program.

  • The iostream header includes operations for reading/writing to files and the console itself, including std::cout.
1
#include <iostream>
  • All functionality used from the standard library will be part of the std namespace.
    • Namespaces allow us to avoid name conflicts for commonly used names.
    • If a feature from a namespace is used often, it can be imported into the global space with using:
      1
      using std::cout;

Cube.h

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* Simple C++ class for representing a Cube.
*
* @author
* Wade Fagen-Ulmschneider <waf@illinois.edu>
*/

// All header (.h) files start with "#pragma once":
#pragma once

// A class is defined with the `class` keyword, the name
// of the class, curly braces, and a required semicolon
// at the end:
class Cube {
public: // Public members:
double getVolume();
double getSurfaceArea();
void setLength(double length);

private: // Private members:
double length_;
};

Cube.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* Simple C++ class for representing a Cube.
*
* @author
* Wade Fagen-Ulmschneider <waf@illinois.edu>
*/

#include "Cube.h"

double Cube::getVolume() {
return length_ * length_ * length_;
}

double Cube::getSurfaceArea() {
return 6 * length_ * length_;
}

void Cube::setLength(double length) {
length_ = length;
}

main.cpp

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
/**
* C++ code for creating a Cube of length 2.4 units.
* - See ../cpp-std/main.cpp for a similar program with print statements.
*
* @author
* Wade Fagen-Ulmschneider <waf@illinois.edu>
*/

#include <iostream>
#include "Cube.h"

int main() {
Cube c;

c.setLength(3.48);
double volume = c.getVolume();
std::cout << "Volume: " << volume << std::endl;

return 0;
}

Stack Memory

In C++, the programmer has control over the memory and lifecycle of every variable! By default, variables live in stack memory.

Stack memory is associated with the current function and the memory’s lifecycle is tied to the function(When the function returns or ends, the stack memory of that function is released).

Stack memory always starts from high addresses and grows down.

Encapsulation

&

In C++, the & operator returns the memory address of a variable.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
/**
* C++ program using the & operator to find the address of a variable in memory.
*
* @author
* Wade Fagen-Ulmschneider <waf@illinois.edu>
*/

#include <iostream>

int main() {
int num = 7;

std::cout << "Value: " << num << std::endl;
std::cout << "Address: " << &num << std::endl;

return 0;
}

Pointer

A pointer is a variable that stores the memory address of the data.

  • Simply put: pointers are a level of indirection from the data.
  • In C++, a pointer is defined by adding an * to the type of the variable.
1
int * p = &num;
  • Given a pointer, a level of indirection can be removed with the dereference operator *.
1
2
3
4
int num = 7;
int * p = &num;
int value_in_num = *p;
*p = 42;

Puzzle

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
double someOtherFunction();  // Forward decl

#include <iostream>
#include "Cube.h"
using uiuc::Cube;

Cube *CreateUnitCube() {
Cube cube;
cube.setLength(15);
return &cube;
}

int main() {
Cube *c = CreateUnitCube();
someOtherFunction();
double a = c->getSurfaceArea();
std::cout << "Surface Area: " << a << std::endl;
double v = c->getVolume();
std::cout << "Volume: " << v << std::endl;
return 0;
}
Stack Memory
Stack Memory

Heap Memory

Heap memory allows us to create memory independent of the lifecycle of a function. - The only way to create heap memory in C++ is with the new operator. - The new operator returns a pointer to the memory storing the data – not an instance of the data itself.

1
int * numPtr = new int;

nullptr

The C++ keyword nullptr is a pointer that points to the memory address 0x0. - nullptr represents a pointer to nowhere - Address 0x0 is reserved and never used by the system - Address 0x0 will always generate an “segmentation fault” when accessed. - Calls to delete 0x0 are ignored

Arrow Operator (->)

When an object is stored via a pointer, access can be made to member functions using the -> operator:

1
2
c -> getVolume();
(*c).getVolume();

C++ program's source code organization

  • .h files are "header files". These usually have definitions of objects and declarations of global functions. Recently, some people name header files with a ".hpp" suffix instead.

  • .cpp files are often called the "implementation files," or simply the "source files". This is where most function definitions and main program logic go.

Take the Cube code above as example:

When you write #include , the compiler will look for the iostream header file in a system-wide library path that is located outside of your current directory.

#include "Cube.h" just like in the Cube.cpp file. You have to include the necessary headers in every cpp file where they are needed. However, you shouldn't use #include to literally include one cpp file in another! There is no need to write #include "Cube.cpp" because the function definitions in the Cube.cpp file will be compiled separately and then linked to the code from the main.cpp file.

The Cube.cpp files and main.cpp files make requests to include various header files. (The compiler might automatically skip some requests because of #pragma once to avoid including multiple times in the same file.) The contents of the requested header files will be temporarily copied into the cpp source code file where they are included. Then, the cpp file with all of its extra included content will be compiled into something called an object file. (Our provided examples keep the object files hidden in a subdirectory, so you don't need to bother with them. But, if you see a file that has a .o extension, that is an object file.) Each cpp file is separately compiled into an object file. So, in this case Cube.cpp will be compiled into Cube.o, and main.cpp will be compiled into main.o.

Class Constructors

Automatic Default Constructor: If we do not provide any custom constructors, the C++ compiler provides an automatic default constructor for our class for free. The automatic default constructor will only initialize all member variables to their default values. If any custom constructor is defined, an automatic default constructor is not defined.

Custom Default Constructor: The simplest constructor we can provide is a custom default constructor that specifies the state of the object when the object is constructed. We define one by creating: - A member function with the same name of the class - The function takes zero parameters. - The function does not have a return type.

1
Cube::Cube()  // custom default constructor

Custom Constructors: We can also specify custom, non-default constructors that require client code to supply arguments.

1
2
Cube::Cube(double length)
// one-argument ctor specifying initial length

Copy Constructors

In C++, a copy constructor is a special constructor that exists to make a copy of an existing object.

copy constructor

If we do not provide a custom copy constructor, the C++ compiler provides an automatic copy constructor for our class for free. The automatic copy constructor will copy the contents of all member variables.

A custom copy constructor is: - Has exactly one argument – The argument must be const reference of the same type as the class.

1
Cube::Cube(const Cube & obj)

Often, copy constructors are invoked automatically:

  • Passing an object as a parameter (by value)
  • Returning an object from a function (by value)
  • Initializing a new object

Assignment Operator

A custom assignment operator is: - Is a public member function of the class. - Has the function name operator=. - Has a return value of a reference of the class’ type. - Has exactly one argument – The argument must be const reference of the class’ type.

1
2
3
4
Cube & Cube::operator=(const Cube & obj) {
length_ = obj.length_;
std::cout << "Assignment operator invoked!" << std::endl; return *this;
}

Variable Storage

In C++, an instance of a variable can be stored directly in memory, accessed by pointer, or accessed by reference.

Variable Storage

Direct Storage

By default, variables are stored directly in memory. - The type of a variable has no modifiers. - The object takes up exactly its size in memory.

1
2
3
Cube c;            // Stores a Cube in memory
int i; // Stores an integer in memory
uiuc::HSLAPixel p; // Stores a pixel in memory

Storage by Pointer

  • The type of a variable is modified with an asterisk (*).
  • A pointer takes a “memory address width” of memory (ex: 64 bits on a 64-bit system).
  • The pointer “points” to the allocated space of the object.
1
2
3
Cube *c;            // Pointer to a Cube in memory
int *i; // Pointer to an integer in memory
uiuc::HSLAPixel *p; // Pointer to a pixel in memory

Storage by Reference

  • A reference is an alias to existing memory and is denoted in the type with an ampersand (&).
  • A reference does not store memory itself, it is only an alias to another variable.
  • The alias must be assigned when the variable is initialized.
1
2
3
Cube &c = cube; // Alias to the variable `cube` 
int &i = count; // Alias to the variable `i` uiuc::HSLAPixel &p; // Illegal! Must alias something
when variable is initialized.

Pass by

Identical to storage, arguments can be passed to functions in three different ways: - Pass by value (default) - Pass by pointer (modified with *) - Pass by reference (modified with &, acts as an alias)

Class Destructor

When an instance of a class is cleaned up, the class destructor is the last call in a class’s lifecycle.

Destructor

An destructor should never be called directly. Instead, it is automatically called when the object’s memory is being reclaimed by the system.

  • If the object is on the stack, when the function returns
  • If the object is on the heap, when delete is used

To add custom behavior to the end-of-life of the function, a custom destructor can be defined as: - A custom destructor is a member function. - The function’s destructor is the name of the class, preceded by a tilde ~. - All destructors have zero arguments and no return type.

1
Cube::~Cube(); // Custom destructor

A custom destructor is essential when an object allocates an external resource that must be closed or freed when the object is destroyed. Examples: - Heap memory - Open files - Shared memory

Creating Templated Types

A template variable is defined by declaring it before the beginning of a class or function

1
2
3
4
5
6
7
8
9
10
11
12
template <typename T>
class List {
...
private:
T data_;

template <typename T>
int max(T a, T b) {
if (a > b) { return a; }
return b;
}
};

Inheritance

A base class is a generic form of a specialized, derived class.

Initialization

When a derived class is initialized, the derived class must construct the base class:

  • Cube must construct Shape
  • By default, uses default constructor
  • Custom constructor can be used with an initialization list
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
/**
* A `Cube` class inheriting from a `Shape`
*
* @author
* Wade Fagen-Ulmschneider <waf@illinois.edu>
*/

#include "Cube.h"
#include "Shape.h"

namespace uiuc {
Cube::Cube(double width, uiuc::HSLAPixel color) : Shape(width) {
color_ = color;
}

double Cube::getVolume() const {
// Cannot access Shape::width_ due to it being `private`
// ...instead we use the public Shape::getWidth(), a public function

return getWidth() * getWidth() * getWidth();
}
}

Access Control

When a base class is inherited, the derived class:

  • Can access all public members of the base class
  • Can not access private members of the base class

Initializer List

The syntax to initialize the base class is called the initializer list and can be used for several purposes:

  • Initialize a base class
  • Initialize the current class using another constructor
  • Initialize the default values of member variables
1
2
3
4
5
6
7
8
9
10
11
12
13
#include "Shape.h"

Shape::Shape() : Shape(1) {
// Nothing.
}

Shape::Shape(double width) : width_(width) {
// Nothing.
}

double Shape::getWidth() const {
return width_;
}