A development environment and a first C++ class
Three hours, three parts:
pvector.hpp and its main in pvector.cpp: header guards, constructor and destructor, overloading of =, + and -, arguments passed by reference, this, const methods, using and constexpr.pvector is the first brick of the simulation codes (Monte Carlo and Molecular Dynamics) we will write in the next labs: positions, velocities and forces of the particles will all be pvectors.
| Linux | macOS | Windows | |
|---|---|---|---|
| Compiler | g++ (GCC) |
clang++ (Apple), also callable as g++ |
g++ inside WSL 2 |
| Terminal | any | Terminal / iTerm2 | Ubuntu (WSL) / Windows Terminal |
| Editor | your choice | your choice | VS Code + WSL extension |
molgl visualizer).WSL = Windows Subsystem for Linux. Version 2 runs a real Linux kernel in a lightweight virtual machine managed by Windows:
apt, g++, make, gdb, …Requirements: Windows 11, or Windows 10 version 2004 (build 19041) or later, and hardware virtualization enabled (it usually is on recent laptops).
Open PowerShell as administrator: right click on Start → Terminal (Admin) (Windows 11) or Windows PowerShell (Admin) (Windows 10).
Run
It enables the required Windows features, installs WSL 2 and Ubuntu.
Restart Windows.
Open the Ubuntu app (Start menu). The first time it asks for a UNIX username and password: choose them and remember the password, it is the one sudo will ask for. While you type it nothing is shown: that is normal.
Check, from PowerShell:
VERSION is 1 → wsl --set-version Ubuntu 2 (use the NAME printed by wsl -l -v), and for the future wsl --set-default-version 2.
Error about virtualization (e.g. 0x80370102) → enable it in the BIOS/UEFI (Intel VT-x or AMD-V / SVM), then retry.
wsl --install only prints the help (Windows 10 not up to date) → run Windows Update first. If it still does not work, enable the two features by hand from an administrator PowerShell, restart, run wsl --update (it installs the WSL 2 kernel) and wsl --set-default-version 2, then install Ubuntu from the Microsoft Store:
Keep WSL up to date: wsl --update; general status: wsl --status.
From the Ubuntu terminal (the same commands on a native Ubuntu/Debian machine). apt is the package manager of Ubuntu; sudo runs a command as administrator and asks for your Linux password:
On macOS the compiler comes with the Xcode command line tools:
On Fedora: sudo dnf install gcc-c++ make gdb.
Later in the course we will also install the libraries for molgl, the program we use to look at the configurations (freeglut, libpng): not today.
Work in your Linux home (~, i.e. /home/<your Linux user>), e.g. ~/csm, not on the Windows disk /mnt/c/...: compiling and running from /mnt/c is much slower.
From Linux, the Windows disk is under /mnt/c. To unpack the zips you downloaded with the browser from the course page:
mkdir -p: no error if the folder already exists; a && b: run b only if a succeeded.
From Windows, the Linux files are in the Linux entry of File Explorer, or at \\wsl.localhost\Ubuntu\home\<your Linux user>.
Quick trick: explorer.exe . opens the current Linux folder in File Explorer.
If you do not have a favourite editor, use Visual Studio Code (free, all systems). On Windows install it on Windows, not inside Ubuntu.
Suggested extensions (Extensions panel, Ctrl+Shift+X, on macOS Cmd+Shift+X):
| Extension | What it gives you |
|---|---|
| WSL (Microsoft) — Windows only | VS Code works on the Linux files and runs the Linux g++ |
| C/C++ (Microsoft) | IntelliSense: completion, errors underlined while you type, info on hover, Go to Definition (F12); the debugger |
| GitHub Copilot | AI suggestions and chat; sign in with a GitHub account: Copilot Free has a monthly limit, Copilot Student is free for verified students (GitHub Education) |
File → Open Folder… and choose the project folder (~/csm/project): VS Code works on a folder, and writes its settings in .vscode/ inside it.
On Windows, open it from the Ubuntu terminal instead:
The first time VS Code installs its server inside Linux; bottom left you then read WSL: Ubuntu. Install the C/C++ extension when it proposes Install in WSL.
IntelliSense and the compiler can disagree: what counts is what g++ says when you compile.
Copilot: useful to understand an error or a piece of code. But the point of the labs is that you write pvector.
The integrated terminal (Terminal → New Terminal): a Linux terminal (in WSL, in Ubuntu) in the project folder. Type the same g++ command as outside. In the first weeks, do it this way: you see what happens.
The ▷ button at the top right of the editor, Run C/C++ File (C/C++ extension): with the file with main open (pvector.cpp, not pvector.hpp), choose C/C++: g++ build and debug active file (on macOS clang++). VS Code compiles the active file, runs it, and writes the recipe in .vscode/tasks.json.
That recipe does not use our options: add them at the beginning of "args" in .vscode/tasks.json:
Ctrl+Shift+B (macOS Cmd+Shift+B, Terminal → Run Build Task) compiles with the same recipe; F5 starts the debugger (we will use it later).
hello.cpp:
std::cout is explained in the next slide. ./hello: the program hello of the current folder (.); Linux does not look for programs there by itself.
-std=c++17 selects the C++ standard; -o hello the name of the executable.-Wall turns on most of the useful warnings of the compiler (an unused variable, for example: Example 1): always use it, and read them.-O2 (optimization) will be important for the simulations, not today.If ./hello prints the line, the environment is ready.
std::coutIn C++ you print with the stream std::cout (header <iostream>), not printf:
Output:
n = 3, t = 0.5
pvector
<< (the shift operator of C, redefined for the output: an example of the operator overloading of Part 2) sends the pieces one after the other; the compiler knows the type of each, so no %d or %f. std::endl ends the line.std::string (header <string>) is the string type of C++: a class (a type defined by a library, Part 2), assigned with = and printed with <<.std:: says that cout, endl and string belong to the C++ standard library; why the prefix is needed (namespaces) we will see in a later lab.printf still works (header <cstdio>), but cannot print C++ objects.Key concepts
Encapsulation
Methods/functions act on objects and return objects (as an operator does) ⇓
Polymorphism — functions/methods can behave differently depending on the objects they act on: function overloading, operator overloading (e.g. + - / *), pointers.
Example: \(a + b\) (scalars) or \(\vec a + \vec b\) (vectors): operator “+” overloading.
Inheritance — in OO paradigm we have custom types called classes and we can enrich a class with more attributes and methods from base one.
From a non OO language (C) to an OO language (C++)
char, int, float, double and operators that act on them (such as ++, --, +, *, etc.).struct) called “classes” and it allows operators and methods to act on them.Note
In C you had struct which could embed attributes but not methods. Hence classes can be also thought as a generalization of “struct”.
int, double, float ← fundamental types of C language.
If one writes
Assume you have a custom type, i.e. a class, called vector which represents a vector. You can instantiate it as follows:
If we have
and we “overloaded” the operators = and +, we can write
Similarly we can overload other operators such as ==, ++, --, *, /, *=, etc.
And this is precisely the goal of the first part of these hands-on classes, i.e.
learn how to write your own class to handle vectors and matrices for computations.
ex0_basic_c_types.cexamples/N0_from_c_to_cpp/ex0_basic_c_types.c
#include<stdio.h>
int main(void)
{
/* basic types */
int a, b, c, i;
double d, e, f;
/* double is a class */
/* d, e and f are instances of the class double */
a=1;
b=2;
c = a + 2;
d = 1.2;
e = 2.3;
f = d + e;
printf("a=%d b=%d c=%d\n", a, b, c);
printf("d=%f e=%f f=%f\n", d, e, f);
/* vectors */
double va[3]={1.2, 1.3, -1.0};
double vb[3]={0.2, 0.1, 0.4};
double vc[3];
// vc = va + vb
for (i = 0; i < 3; i++)
{
vc[i] = va[i] + vb[i]; }
printf("va=(");
for (i = 0; i < 3; i++)
{
printf("%f", va[i]);
if (i < 2)
printf(",");
}
printf("\n");
printf("vb=(");
for (i = 0; i < 3; i++)
{
printf("%f", vb[i]);
if (i < 2)
printf(",");
}
printf("\n");
printf("vc=va+vb=(");
for (i = 0; i < 3; i++)
{
printf("%f", vc[i]);
if (i < 2)
printf(",");
}
printf(")\n");
}a=1 b=2 c=3
d=1.200000 e=2.300000 f=3.500000
va=(1.200000,1.300000,-1.000000
vb=(0.200000,0.100000,0.400000
vc=va+vb=(1.400000,1.400000,-0.600000)
double we write f = d + e; for vectors vc = va + vb is not possible: every component has to be spelled out with a loop.va, vb and vc).gcc compiles C, g++ C++: these two examples are C files.double is a fundamental type, but it behaves like a class here (objects and operators).ex1_vec_with_funcs.cexamples/N0_from_c_to_cpp/ex1_vec_with_funcs.c
#include<stdio.h>
void sumvec(double va[3], double vb[3], double vc[3]);
void printvec(double vec[3], char* txt);
int main(void)
{
/* basi types */
int i;
double va[3]={1.2, 1.3, -1.0}, vb[3]={0.2, 0.1, 0.4}, vc[3];
/* ... or better with functions */
sumvec(va, vb, vc);
printvec(va, "va");
printvec(vb, "vb");
printvec(vc, "vc=va+vb");
}
void printvec(double vec[3], char* txt)va=(1.200000,1.300000,-1.000000)
vb=(0.200000,0.100000,0.400000)
vc=va+vb=(1.400000,1.400000,-0.600000)
sumvec(va, vb, vc) is not vc = va + vb.sumvec can fill vc. char* txt is a C string (compiled with g++ it warns: in C++ "va" is a const char*).double[3]) and functions acting on them live separately: nothing tells the compiler that sumvec belongs to “vectors”.vector that carries its data and its operations.-Wall also warns that i in main is never used: warning: unused variable 'i'.ex2_a_cpp_class.hppexamples/N0_from_c_to_cpp/ex2_a_cpp_class.hpp
#include<iostream>
#include<string>
class myclass
{
//private:
int a;
std::string txt; // private member (an int)
void show_private(void) const // private method/function
{
std::cout << "no\n!" << std::endl;
}
public:
void show(void) const
{
show_private();
std::cout << txt << " " << a << " " << std::endl;a, txt) and functions, the methods, which use data and other methods directly (show() calls show_private()); const: show does not change the object (N1).private is the default in a class (not in a struct): main can call C.show(), not C.show_private() nor read C.a (error: 'int myclass::a' is private ...).myclass(): same name as the class, no return type, runs when the object is created; destructor ~myclass(): runs when it is destroyed.ex2_a_cpp_class.cppexamples/N0_from_c_to_cpp/ex2_a_cpp_class.cpp
myclass C;: the class name is already a type (in C you would write struct). A method is called with a dot, as for the fields of a C struct: C.show().C is born and when it goes out of scope (at the end of main)."no\n!" contains a newline: that is why no and ! are on two lines..hpp), main in a .cpp that #includes it: we compile only the .cpp. #include "..." is for our own files (looked for first in the folder of the file), #include <...> for the standard headers.C
a, b, c are instances of it (objects).a.v[0] = 1.0 on a pvector does not compile: v is private. If tomorrow we change how the data are stored, the code that uses pvector does not change.a + b on two pvectors is an overloaded +.myvec.hpp#include<iostream>
class myvec
{
// private members
int v[3]; // array of integers
int len; // allocated int
public:
// constructor
myvec()
{
len=3;
}
// destructor
~myvec()
{
// do nothing
}
// assign value to (i+1)-th element of the array
void set(int i, int val)
{
v[i] = val;
}
// get (i+1)-th element of array
int get(int i) const // pass implicitly argument (*this) with const modifier,
//it means that this method does not change the state of the object
{
return v[i];
}
// get actual length of array
int length(void) const
{
return len;
}
};myvec.cppexamples/N1_a_first_vec_class/myvec.cpp
#include "./myvec.hpp"
int main(int argc, char** argv)
{
// class instantiation
myvec v;
// initialize array elements
for (int i=0; i < v.length(); i++)
{
v.set(i, 0);
}
// print out value of third element
std::cout << "v[2]=" << v.get(2) << std::endl;
// set 2-th element
v.set(1, 1);
// set 3-th element
v.set(2, -2);
std::cout << "v[1]=" << v.get(1) << " v[2]=" << v.get(2) << std::endl;
}const method (get, length): it does not change the object. Every method receives implicitly the object it is called on (the *this of the comment, see Part 3); const makes it read-only. argc, argv: command line, as in C.vecder.hppexamples/N2_a_derived_vec_class/vecder.hpp
#include<iostream>
#include"../N1_a_first_vec_class/myvec.hpp"
class vecder: public myvec
{
public:
// we add just a show public method to this derived class
void show(void) const
{
std::cout << "(";
for (int i = 0; i < length(); ++i) {
// note that length() is a public method of base class
{
std::cout << get(i); // get() is another public method of public class
}
if (i < length()-1)
std::cout << ",";
else
std::cout << ")" << std::endl;
}
}
};class vecder: public myvec: vecder inherits everything from myvec, and its public interface stays public; it adds the method show().v[i] in show() would not compile: v is private in myvec, not even a derived class sees it. The way out (protected) is for next lab.vecder.cppexamples/N2_a_derived_vec_class/vecder.cpp
(0,1,2)
set() and length() come from the base class myvec, show() from vecder.vecder has no constructor: the one generated by the compiler calls myvec() first, which sets len=3. The base part is always built first.pvectorWrite from scratch an efficient class for vectors, with methods and overloaded operators: pvector.hpp (the class) and pvector.cpp (a main to test it), in the folder project/ of project_classes.zip (course page).
The header begins with a TODO list: [*] = to do today, [ ] = next labs.
// TODO LIST:
// * = TODO, X = done, E = exercise
// [*] standard constructor/destructor
// [*] constructor
// [*] show()
// [*] overloading of = assignment
// [*] overloading of + operator for addition of vectors
// [*] overloading of - operator for substraction of vectors
// [*] sum() method to sum two vectors
// [*] get( ) (get i-th element)
// [*] set( , ) (set i-th element)
// [ ] constructor with overloading->initializer_list
// [ ] overloading of += and -= operators with vectors
// ...The skeleton already contains all the signatures, and operator= is implemented: your job is to fill the empty bodies.
#include and the header guardpvector.hpp begins and ends like this:
#include is a preprocessor directive: before compiling, the content of the header is copied in its place. <iostream> gives std::cout, <string> gives std::string, <cmath> the math functions (sqrt, … for norm() later).#ifndef, #define, #endif are also preprocessor directives: together they form the header guard, next slide..cpp. If a .cpp includes the same header twice, also through another header, the class is defined twice. Example N2 without guards: vecder.hpp already includes myvec.hpp, so a main that includes both stops with error: redefinition of 'class myvec'._PVECTOR_ is not defined, so the file is read and the macro defined; the second time everything between #ifndef and #endif is skipped. Each header needs its own name.NT and ntypeNT with 3 before compiling. No type, no scope.
constexpr: a typed constant that the compiler can compute at compile time — so it can size an array. In C++ it is the preferred way.
using defines an alias of a type, exactly like typedef. Change this single line to float and the whole class works in single precision.
NT equal to 2 you get vectors in the plane, with no other change in the class.pvector<double,3>, pvector<float,2>.The void constructor (also called default constructor) is the only constructor you write today: it runs every time you write pvector P;, and it must leave the vector in a known state. Without it the components would contain garbage, like a local double v[3] in C.
Initialize the NT components with values of your choice — not necessarily zero: all 0, v[i] = i, … Then change them with set().
The destructor has nothing to do here: v is a fixed-size array, it disappears with the object. Leave it empty (we will need it when we allocate memory dynamically).
show(): default arguments and constDefault argument: if the caller omits it, s is "".
const method: it only reads the object. The compiler checks it (assigning v[i] inside show() is an error), and only const methods can be called on a const object:
Use std::cout with a loop over the NT components, and a comma between them.
int &x means reference; in an expression &a is still, as in C, the address of a.In the class all the vector arguments are passed as const pvector&:
&: no copy of the object (for a vector of 3 doubles it is cheap, but think of a configuration of \(10^5\) particles…).const: the method promises not to modify V2; the compiler enforces it.pvector you can read the private members of another pvector: V2.v[i] is legal. Access is per class, not per object.operator= and thisThe one method already implemented in the skeleton:
A = B is just a nicer way to write the method call A.operator=(B): A is the object the method acts on, B the argument.
this is a pointer: the address of the object the method has been called on (A). As in C, * applied to an address gives the object, so *this is A itself. v[i] is a shorthand for (*this).v[i].
A itself by reference (pvector&): A = B = C; works as for double — it is A = (B = C) — and no copy of the vector is made.const: it changes the object.A = B works: the compiler generates a copy member by member, as for C structs. We write it to see how an operator is defined.sum(), operator+ and operator-Recipe, the same for the three:
VT;v[i] (of A) and V2.v[i] (of B);return VT;const: A + B must change neither A nor B.pvector, not pvector&): VT is a local object, destroyed when the method ends. A reference to it would point to a dead object (g++ warns: reference to local variable returned).sum() and operator+ do the same thing: the operator is just nicer to read. Once one is written, the other can call it.get() and set()get is const, set is not: why?set in the skeleton returns an ntype: return val (or change it to void, as in myvec).0 to NT-1, as in C. Nothing checks the index: get(3) reads outside the array (undefined behaviour).Read the warnings
The empty bodies of the skeleton compile: for a method that should return a value and has no return the compiler only warns no return statement in function returning non-void [-Wreturn-type]. But when the program calls it, it crashes (Illegal instruction) or worse (undefined behaviour): fill a method before testing it.
main: pvector.cppThe skeleton only declares three vectors. Test every method, for example:
#include "./pvector.hpp"
int main(void)
{
pvector A, B, C; // the void constructor runs for the three vectors
C.show("C="); // the values set by pvector()
A.set(0, 1.0); A.set(1, 2.0); A.set(2, 3.0);
B.set(0, 0.5); B.set(1, -1.0); B.set(2, 2.0);
A.show("A=");
B.show("B=");
std::cout << "A(1)=" << A.get(1) << "\n";
C = A + B; C.show("A+B=");
C = A - B; C.show("A-B=");
C = A.sum(B); C.show("A.sum(B)=");
pvector E, F;
E = F = A; // chained assignment: operator= returns pvector&
E.show("E=");
const pvector PC;
PC.show("PC="); // ok only because show() is const
return 0;
}Expected output, if pvector() sets all components to 0 (otherwise C= and PC= show the values you chose):
C=(0,0,0)
A=(1,2,3)
B=(0.5,-1,2)
A(1)=2
A+B=(1.5,1,5)
A-B=(0.5,3,1)
A.sum(B)=(1.5,1,5)
E=(1,2,3)
PC=(0,0,0)
-Wall: if there are, read them — they are usually a bug.ntype to float: everything must still compile and run. With NT 2 the class still works, but this main does not: set(2, ...) writes outside the array, and nothing checks it.; after the class: class pvector { ... };. Without it: error: expected ';' after class definition.pvector P(); is not an object: it declares a function P returning a pvector. Write pvector P;.operator+: it compiles with a warning (reference to local variable 'VT' returned), then with g++ it usually crashes (Segmentation fault): undefined behaviour.const on show() or get(): then they cannot be called on a const pvector, nor on an argument const pvector& V2: passing 'const pvector' as 'this' argument discards qualifiers.return in a non-void method: only a warning, then a crash (see get() and set()).error: redefinition of 'class ...' as soon as the header is included twice.Next lab: protected members, more overloading (+=, -=, scalar product, norm(), vector times scalar), function overloading. Later: a second constructor, with initializer_list, to write pvector V = {1,2,3};.