for Robot Artificial Inteligence

9. Smart pointers, Associative containers, type casting, Enumeration classes, Read/write binary files.

|

Smart pointers

  • Smart pointers wrap a raw pointer into a class and manage its lifetime (RAII)
  • Smart pointers are all about ownership
  • Always use smart pointers when the pointer should own heap memory
  • Only use them with heap memory!
  • Still use raw pointers for non-owning pointers and simple address storing
  • #include to use smart pointers
  • We will focus on 2 types of smart pointers:
    • std::unique_ptr
    • std::shared_ptr

Smart pointers manage memory!

  • Smart pointers apart from memory allocation behave exactly as raw pointers:
    • Can be set to nullptr
    • Use * ptr to dereference ptr
    • Use ptr-> to access methods
    • Smart pointers are polymorphic
  • Additional functions of smart pointers:
    • ptr.get() returns a raw pointer that the smart pointer manages
    • ptr.reset(raw_ptr) stops using currently managed pointer, freeing its memory if needed, sets ptr to raw_ptr

Unique pointer (std::unique_ptr)

  • Constructor of a unique pointer takes ownership of a provided raw pointer
  • No runtime overhead over a raw pointer
  • Syntax for a unique pointer to type Type:
    #include <memory>
    // Using default constructor Type();
    auto p = std :: unique_ptr <Type >(new Type);
    // Using constructor Type(<params >);
    auto p = std :: unique_ptr <Type >(new Type(<params >));
    
  • From C++14 on:
    // Forwards <params > to constructor of unique_ptr
    auto p = std :: make_unique <Type>(<params >);
    

What makes it “unique”

  • Unique pointer has no copy constructor
  • Cannot be copied, can be moved
  • Guarantees that memory is always owned by a single unique pointer
#include <iostream>
#include <memory>
struct A{
  int a = 10;
};
int main () {
  auto a_ptr = std::unique_ptr<A>(new A); // a_ptr are pointing a A structe
  std::cout << a_ptr ->a << std::endl;
  auto b_ptr = std::move(a_ptr);
  std::cout << b_ptr ->a << std::endl;
  return 0;
}

Shared pointer (std::shared_ptr)

  • Constructed just like a unique_ptr
  • Can be copied
  • Stores a usage counter and a raw pointer
    • Increases usage counter when copied
    • Decreases usage counter when destructed
  • Frees memory when counter reaches 0
  • Can be initialized from a unique_ptr
#include <memory>
// Using default constructor Type();
auto p = std::shared_ptr<Type >(new Type);
auto p = std::make_shared <Type >(); // Preferred
// Using constructor Type(<params >);
auto p = std::shared_ptr<Type >(new Type(<params>));
auto p = std::make_shared <Type>(<params>); // Preferred

Shared pointer

#include <iostream>
#include <memory>
struct A{
  A(int a) { std::cout << "I'm alive!\n"; }
  ~A() { std::cout << "I'm dead... :(\n"; }
};
int main(){
  // Equivalent to: std::shared_ptr <A>(new A(10));
  auto a_ptr = std :: make_shared <A >(10);
  std::cout << a_ptr.use_count() << std::endl;//1
  {
    auto b_ptr = a_ptr;
    std::cout << a_ptr.use_count() << std::endl; //2
  }
  std :: cout << "Back to main scope\n";
  std :: cout << a_ptr. use_count () << std :: endl;
  return 0;
}
  • result
I'm alive
1
2
Back to main scope
1
i'm dead...

When to use what?

  • Use smart pointers when the pointer must manage memory
  • By default use unique_ptr
  • If multiple objects must share ownership over something, use a shared_ptr to it
  • Using smart pointers allows to avoid having destructors in your own classes
  • Think of any free standing new or delete as of a memory leak or a dangling pointer:
    • Don’t use delete
    • Allocate memory with make_unique, make_shared
    • Only use new in smart pointer constructor if cannot use the functions above

Typical beginner error

#include <iostream>
#include <memory>
int main () {
  int a = 0;
  // Same happens with std::shared_ptr.
  auto a_ptr = std::unique_ptr<int>(&a);
  return 0;
}
  • return
    *** Error in `file ': free ():
    2 invalid pointer: 0 x00007fff30a9a7bc ***
    
  • Create a smart pointer from a pointer to a stack-managed variable
  • The variable ends up being owned both by the smart pointer and the stack and gets deleted twice → Error!

good exmple using smart pointers

#include <iostream>
#include <vector>
#include <memory>
using std :: cout; using std :: unique_ptr ;
struct AbstractShape { // Structs to save space.
  virtual void Print () const = 0;
};
struct Square : public AbstractShape{
  void Print () const override { cout << "Square\n"; }
};
struct Triangle : public AbstractShape{
  void Print () const override { cout << "Triangle\n"; }
};
int main(){
  std::vector<unique_ptr<AbstractShape>> shapes;
  shapes.emplace_back (new Square);
  auto triangle = unique_ptr<Triangle>(new Triangle);
  shapes.emplace_back(std::move(triangle));
  for (const auto& shape : shapes) { shape ->Print (); }
  return 0;
}

Associative containers - std::map

  • #include <map> to use std::map
  • Stores items under unique keys
  • Implemented usually as a Red-Black tree
  • Key can be any type with operator < defined
  • Create from data:
    std ::map <KeyT , ValueT > m = {
    {key , value}, {key , value}, {key , value }};
    
  • Add item to map: m.emplace(key, value);
  • Modify or add item: m[key] = value;
  • Get (const) ref to an item: m.at(key);
  • Check if key present: m.count(key) > 0;
  • Check size: m.size();

std::unordered_map

  • **#include ** to use **std::unordered_map**
  • Serves same purpose as std::map
  • Implemented as a hash table
  • Key type has to be hashable
  • Typically used with int, string as a key
  • Exactly same interface as std::map

Iterating over maps

for (const auto& kv : m) {
  const auto& key = kv.first;
  const auto& value = kv.second;
  // Do important work.
}
  • Every stored element is a pair
  • map has keys sorted
  • unordered_map has keys in random order

Type casting(角色分配) - Casting type of variables

  • Every variable has a type
  • Types can be converted from one to another
  • Type conversion is called type casting
  • There are 3 ways of type casting:
    • static_cast
    • reinterpret_cast
    • dynamic_cast

      static_cast

  • Syntax: static_cast(variable)
  • Convert type of a variable at compile time
  • Rarely needed to be used explicitly
  • Can happen implicitly for some types,
    • e.g. float can be cast to int
  • Pointer to an object of a Derived class can be upcast to a pointer of a Base class
  • Enum value can be caster to int or float
  • Full specification is complex!

reinterpret_cast

  • Syntax: reinterpret_cast(variable)
  • Reinterpret the bytes of a variable as another type
  • We must know what we are doing!
  • Mostly used when writing binary data

dynamic_cast

  • Syntax: dynamic_cast<Base*>(derived_ptr)
  • Used to convert a pointer to a variable of Derived type to a pointer of a Base type
  • Conversion happens at runtime
  • If derived_ptr cannot be converted to Base* returns a nullptr
  • returns a nullptr

Enumeration classes

  • Store an enumeration of options
  • Usually derived from int type
  • Options are assigned consequent numbers
  • Mostly used to pick path in switch
    enum class EnumType { OPTION_1 , OPTION_2 , OPTION_3 };
    
  • Use values as:
    • EnumType::OPTION_1, EnumType::OPTION_2, …
  • GOOGLE-STYLE Name enum type as other types, CamelCase
  • GOOGLE-STYLE Name values as constants kSomeConstant or in ALL_CAPS
#include <iostream>
#include <string>
using namespace std;
enum class Channel{STDOUT, STDERR};
void Print(Channel print_style, const string& msg){
  switch (print_style){
    case Channel::STDOUT:
      cout << msg << endl;
      break;
    case Channel::STDERR:
      cout << msg << endl;
      break;
    default:
      cerr << "Skipping\n";
  }
}

int main () {
  Print(Channel::STDOUT, "hello");
  Print(Channel::STDERR, "World");
}

Explicit values

  • By default enum values start from 0
  • We can specify custom values if needed
  • Usually used with default values
    enum class EnumType {
    OPTION_1 = 10, // Decimal.
    OPTION_2 = 0x2 , // Hexacedimal.
    OPTION_3 = 13
    };
    

Read/write binary files - Writing to binary files

  • We write a sequence of bytes
  • We must document the structure well, otherwise noone can read the file
  • Writing/reading is fast
  • No precision loss for floating point types
  • Substantially smaller than ascii-files
  • Syntax
    file.write(reinterpret_cast <char*>(&a), sizeof(a));
    

Writing to binary files

#include <fstream> // for the file streams
#include <vector>
using namespace std;
int main(){
  string file_name = "image.dat";
  ofstream file(file_name ,
                ios_base :: out | ios_base :: binary);
  if (! file) { return EXIT_FAILURE ; }
  int r = 2; int c = 3;
  vector<float> vec(r * c, 42);
  file.write(reinterpret_cast <char * >(&r), sizeof(r));
  file.write(reinterpret_cast <char*>(&c), sizeof(c));
  file.write(reinterpret_cast <char*>(& vec.front ()),
            vec.size () * sizeof(vec.front ()));
  return 0;
}

Reading from binary files

  • We read a sequence of bytes
  • Binary files are not human-readable
  • We must know the structure of the contents
  • Syntax
    • file.read(reinterpret_cast <char*>(&a), sizeof(a));

Reading from binary files

#include <fstream>
#include <iostream>
#include <vector>
using namespace std;
int main(){
  string file_name = "image.dat";
  int r = 0, c = 0;
  ifstream in(file_name , ios_base ::in | ios_base :: binary);
  if (!in) { return EXIT_FAILURE ; }
  in.read(reinterpret_cast <char*>(&r), sizeof(r));
  in.read(reinterpret_cast <char*>(&c), sizeof(c));
  cout << "Dim: " << r << " x " << c << endl;
  vector <float > data(r * c, 0);
  in.read(reinterpret_cast <char*>(& data.front ()), data.size () * sizeof(data.front ()));
  for (float d : data) { cout << d << endl; }
  return 0;
}

Reference

https://www.ipb.uni-bonn.de/teaching/modern-cpp/

Cpp Core Guidelines: https://github.com/isocpp/CppCoreGuidelines

Git guide: http://rogerdudler.github.io/git-guide/

C++ Tutorial: http://www.cplusplus.com/doc/tutorial/

Book: Code Complete 2 by Steve McConnell

Modern CMake Tutorial https://www.youtube.com/watch?v=eC9-iRN2b04

Compiler Explorer: https://godbolt.org/ Gdbgui: https://www.gdbgui.com/ CMake website: https://cmake.org/ Gdbgui tutorial: https://www.youtube.com/watch?v=em842geJhfk

Fluent C++: structs vs classes: https://google.github.io/styleguide/cppguide.html#Structs_vs._Classes

Comment  Read more

8. pointer, stack and heap, memory leaks, dangling pointers

|

Using pointers for classes

  • Pointers can point to objects of custom classes:
std :: vector <int> vector_int ;
std :: vector <int>* vec_ptr = & vector_int ;
MyClass obj;
MyClass* obj_ptr = &obj;
  • Call object functions from pointer with ->
    MyClass obj;
    obj.MyFunc();
    MyClass* obj_ptr = &obj;
    obj_ptr ->MyFunc ();
    
  • obj->Func() ↔ (* obj).Func()

Pointers are polymorphic

  • Pointers are just like references, but have additional useful properties:
    • Can be reassigned
    • Can point to ‘‘nothing’’ (nullptr)
    • Can be stored in a vector or an array
  • Use pointers for polymorphism
Derived derived;
Base* ptr = &derived;
  • Example: for implementing strategy store a pointer to the strategy interface and initialize it with nullptr and check if it is set before calling its methods
#include <iostream>
#include <vector>
using std::cout;
struct AbstractShape {
  virtual void Print () const = 0;
};
struct square : public AbstractShape{
  void Print () const override {cout << "Square";}
};
struct triangle : public AbstractShape{
  void Print () const override {cout << "triangle";}
};
int main () {
  std::vector<AbstractShape * > shapes;
  Square square;
  Triangle triangle;
  shapes. push_back (& square);
  shapes. push_back (& triangle);
  for (const auto* shape : shapes) { shape ->Print (); }
  return 0;
}

this pointer

  • Every object of a class or a struct holds a pointer to itself
  • This pointer is called this
  • Allows the objects to:
    • Return a reference to themselves: return * this;
    • Create copies of themselves within a function
    • Explicitly show that a member belongs to the current object: this->x();

Using const with pointers

  • Pointers can point to a const variable:
    // Cannot change value , can reassign pointer.
    const MyType* const_var_ptr = &var;
    const_var_ptr = & var_other ;
    
  • Pointers can be const:
    // Cannot reassign ponter , can change value.
    MyType* const var_const_ptr = &var;
    var_const_ptr ->a = 10;
    
  • Pointers can do both at the same time:
    // Cannot change in any way, read -only
    const MyType* const const_var_const_ptr = &var;
    
  • Read from right to left to see which const refers to what

Memory management structures

  • Working memory is divided into two parts:

Stack memory

  • Static memory
  • Available for short term storage (scope)
  • Small / limited (8 MB Linux typisch)
  • Memory allocation is fast
  • LIFO (Last in First out) structure
  • Items added to top of the stack with push
  • Items removed from the top with pop

  • result: 42 4952101

  • only pointing first array in here until it give a certain array index to pointer.

Heap Memory

  • Dynamic memory
  • Available for long time (program runtime)
  • Raw modifications possible with new and delete (usually encapsulated within a class)
  • Allocation is slower than stack allocations

Operators new and new[]

  • User controls memory allocation (unsafe)
  • Use new to allocate data:
    // pointer variable stored on stack
    int* int_ptr = nullptr;
    // 'new' returns a pointer to memory in heap
    int_ptr = new int;
    // also works for arrays
    float* float_ptr = nullptr;
    // 'new' returns a pointer to an array on heap
    float_ptr = new float[number ];
    
  • new returns an address of the variable on the heap
  • Prefer using smart pointers!
  • Memory is not freed automatically!
  • User must remember to free the memory
  • Use delete or delete[] to free memory: ```c++ int* int_ptr = nullptr; int_ptr = new int; // delete frees memory to which the pointer points delete int_ptr;

// also works for arrays float* float_ptr = nullptr; float_ptr = new float[number ]; // make sure to use ‘delete[]’ for arrays delete[] float_ptr ;

- Prefer using smart pointers!

### Example: heap memory
```c++
#include <iostream>
using std::cout; using std::endl;
int main () {
  int size = 2; int* ptr = nullptr;
  {
    ptr = new int[size ];
    ptr [0] = 42; ptr [1] = 13;
  }
  // End of scope does not free heap memory!
  // Correct access , variables still in memory.
  for (int i = 0; i < size; ++i) {
    cout << ptr[i] << endl;
  }
  delete[] ptr; // Free memory.
  for (int i = 0; i < size; ++i) {
    // Accessing freed memory. UNDEFINED!
    cout << ptr[i] << endl;
  }
  return 0;
}

Memory Leak

  • Can happen when working with Heap memory if we are not careful
  • Memory leak: memory allocated on Heap access to which has been lost

#include <iostream>
using std::cout; using std::endl;
int main () {
  double * ptr_1 = NULL;
  double * ptr_2 = NULL;
  int size = 10;
  // Allocate memory for two arrays on the heap
  ptr_1 = new double[size];
  ptr_2 = new double[size];
  cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl;
  ptr_2 = ptr_1;
  // ptr_2 overwritten , no chance to access the memory.
  cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl;
  delete[] ptr_1;
  delete[] ptr_2;
  return 0;
}

  • The memory under address 0x10a3070 is never freed
  • Instead we try to free memory under 0x10a3010 twice
  • Freeing memory twice is an error

Memory leak example

#include <iostream>
#include <cmath>
#include <algorithm>
using std::cout; using std::endl;
int main() {
  double * data = nullptr;
  size_t size = pow (1024 , 3) / 8; // Produce 1GB
  for (int i = 0; i < 5; ++i) {
    // Allocate memory for the data.
    data = new double[size ];
    std :: fill(data , data + size , 1.23);
    // Do some important work with the data here
    cout << "Iteration: " << i << " done!" << endl;
  }
  // This will only free the last allocation!
  delete[] data;
int unused; std :: cin >> unused; // Wait for user.
return 0;
}
  • result
    1 Iteration : 0 done!
    2 Iteration : 1 done!
    3 Iteration : 2 done!
    4 Iteration : 3 done!
    5 terminate called after throwing an instance of 'std ::
    bad_alloc '
    6 what (): std :: bad_alloc
    
  • If we run out of memory an std::bad_alloc error is thrown
  • Be careful running this example, everything might become slow

Dangling pointer(Memory Leak 반대개념)

int* ptr_1 = some_heap_address ;
int* ptr_2 = some_heap_address ;
delete ptr_1;
ptr_1 = nullptr;
// Cannot use ptr_2 anymore! Behavior undefined!
  • Dangling Pointer: pointer to a freed memory
  • Think of it as the opposite of a memory leak
  • Dereferencing a dangling pointer causes undefined behavior

    Dangling pointer example

    #include <iostream>
    using std::cout; using std::endl;
    int main () {
    int size = 5;
    int* ptr_1 = new int[size ];
    int* ptr_2 = ptr_1; // Point to same data!
    ptr_1 [0] = 100; // Set some data.
    cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl;
    cout << "ptr_2[0]: " << ptr_2 [0] << endl;
    delete[] ptr_1; // Free memory.
    ptr_1 = nullptr;
    cout << "1: " << ptr_1 << " 2: " << ptr_2 << endl;
    // Data under ptr_2 does not exist anymore!
    cout << "ptr_2[0]: " << ptr_2 [0] << endl;
    return 0;
    }
    

    Even worse when used in functions

    #include <stdio.h>
    // data processing
    int* GenerateData(int size);
    void UseDataForGood (const int* const data , int size);
    void UseDataForBad (const int* const data , int size);
    int main () {
    int size = 10;
    int* data = GenerateData (size);
    UseDataForGood (data , size);
    UseDataForBad (data , size);
    // Is data pointer valid here? Should we free it?
    // Should we use 'delete[]' or 'delete '?
    delete[] data; // ?????????????
    return 0;
    }
    

Memory leak or dangling pointer

void UseDataForGood (const int* const data , int size) {
  // Process data , do not free. Leave it to caller.
}
void UseDataForBad (const int* const data , int size) {
  delete[] data; // Free memory!
  data = nullptr; // Another problem - this does nothing!
}
  • Memory leak if nobody has freed the memory
  • Dangling Pointer if somebody has freed the memory in a function

RAII

  • Resource Allocation Is Initialization.
  • New object → allocate memory
  • Remove object → free memory
  • Objects own their data!
    struct SomeOtherClass{};
    class MyClass {
    public:
    MyClass () { data_ = new SomeOtherClass ; }
    ~MyClass () {
      delete data_;
      data_ = nullptr;
    }
    private:
    SomeOtherClass * data_;
    };
    int main()
    {
    MyClass a; //copy constructure
    MyClass b(a); // copy object
    return 0;
    }
    
  • result
    • samd data allocated
      1 *** Error in `raii_example ':
      2 double free or corruption : 0 x0000000000877c20 ***
      

Shallow vs deep copy

  • Shallow copy: just copy pointers, not data
  • Deep copy: copy data, create new pointers
  • Default copy constructor and assignment operator implement shallow copying
  • RAII + shallow copy → dangling pointer
  • RAII + Rule of All Or Nothing → correct
  • Use smart pointers instead!

Reference

https://www.ipb.uni-bonn.de/teaching/modern-cpp/

Cpp Core Guidelines: https://github.com/isocpp/CppCoreGuidelines

Git guide: http://rogerdudler.github.io/git-guide/

C++ Tutorial: http://www.cplusplus.com/doc/tutorial/

Book: Code Complete 2 by Steve McConnell

Modern CMake Tutorial https://www.youtube.com/watch?v=eC9-iRN2b04

Compiler Explorer: https://godbolt.org/ Gdbgui: https://www.gdbgui.com/ CMake website: https://cmake.org/ Gdbgui tutorial: https://www.youtube.com/watch?v=em842geJhfk

Fluent C++: structs vs classes: https://google.github.io/styleguide/cppguide.html#Structs_vs._Classes

Comment  Read more

7. Static Variables, Method, Representation of numbers in memory, Raw C array, Non-owning pointers in C++, Classses in memory

|

Static variables and methods

Static member variables of a class

  • Exist exactly once per class, not per object
  • The value is equal accross all instances
  • Must be defined in * .cpp files

    Static member functions of a class

  • Do not have an object of a class
  • Can access private members but need an object
  • Syntax for calling:
    • ClassName::MethodName()

      Static variables

      #include <iostream >
      using std::cout; using std::endl;
      struct Counted {
      Counted () {Counted::count ++; }
      ~Counted () {Counted::count --; }
      static int count; // Static counter member.
      }
      int Counted::count = 0; // Definition.
      int main () {
      Counted a, b;
      cout << "Count: " << Counted :: count << endl; //output 2
      Counted c;
      cout << "Count: " << Counted :: count << endl; //output 3
      return 0;
      }
      

Static member functions

#include <math.h>
#include <iostream>
using std::cout; using std::endl;
class Point{
public:
  Point(int x, int y) : x_{x}, y_{y} {}
  static float dist(const Point& a, const Point& b)
  {
    int diff_x = a.x_ - b.x_;
    int diff_y = a.y_ - b.y_;
    return sqrt(diff_x * diff_x + diff_y * diff_y);
  }
private:
  int x_ = 0;
  int y_ = 0;
}
int main()
{
  Point a(2, 2), b(1, 1);
  cout << "Dist is " << Point::dist(a, b) << endl;
  return 0;
}

Recalling variable declaration

  • int x = 1;
  • float y = 1.1313f; How is the number represented in the memory?

How much memory does a type need?

Example sizeof()

1 // machine specific type sizes
2 sizeof(bool) == 1 byte;
3 sizeof(char) == 1 byte;
4 // floating point types
5 sizeof(float) == 4 bytes;
6 sizeof(double) == 8 bytes;
7 sizeof(long double) == 16 bytes;
8 // integral data types
9 sizeof(short int) == 2 bytes;
10 sizeof(unsigned short int) == 2 bytes;
11 sizeof(int) == 4 bytes;
12 sizeof(unsigned int) == 4 bytes;
13 sizeof(long int) == 8 bytes;
14 sizeof(unsigned long int) == 8 bytes;

Representing integer types

#include <iostream >
using std :: cout;
int main(){
  unsigned short int k = 37;
  cout << "sizeof(" << k << ") is " << sizeof(k)
       << " bytes or " << sizeof(k) * 8 << " bits.";
//output sizeof(37) is 2 bytes or 16 bit
}

Representable intervals

Floating point numbers

#include <iostream >
using std :: cout;
int main(){
  floak k = 3.14159;
  cout << "sizeof(" << k << ") is " << sizeof(k)
       << " bytes or " << sizeof(k) * 8 << " bits.";
//output sizeof(3.141590) is 4 bytes or 32 bit
}

float vs. double

  • Same representation as float
  • double takes 8 bytes instead of 4 for float
  • Longer Exponent und Mantissa.
    • Exponent = 11 Bits instead of 8 for float
    • Mantissa = 53 Bits instead of 23 for float

      What can we represent?

Limited number of significant digits

Digits extinction

#include <cmath>
#include <iostream>
using std::cout; using std::endl;
int main () {
  float pi = M_PI;
  float big_number = 1e7;
  cout << "Pi before: " << pi << endl;
  pi += big_number ;
  pi -= big_number ;
  cout << "Pi after: " << pi << endl;
  cout << "Difference: " << M_PI - pi << endl;
  return 0;
}
  • result:
    • pi before : 3.14159
    • pi after : 3
    • difference: 0.141593

C style arrays

  • Base for std::array, std::vector,std::string
  • Indexing begins with 0!
  • Elements of an array lie in continuous memory.

Declaration

Type array_name[length];
Type array_name[length] = {n0, n1, n2, ..., nX};
Type array_name[] = { n1, n2, n3};

Arrays are simple data containers

int main () {
  int shorts [5] = {5, 4, 3, 2, 1};
  double doubles [10];
  char chars [] = {'h', 'a', 'l', 'l', 'o'};
  shorts [3] = 4;
  chars [1] = 'e';
  chars [4] = chars [2];
  doubles [1] = 3.2;
}
  • Have no methods
  • Do not explicitly store their size

Arrays and sizeof()

  • sizeof() of an array is sizeof() *
    int shortA [5] = {5, 4, 3, 2, 1};
    double longA [4] = {1.0 , 1.1, 1.2, 1.3};
    sizeof(shortA) = 20
    sizeof(shortA) / sizeof(shortA [0]) = 5
    sizeof(longA) = 32
    sizeof(longA) / sizeof(longA [0]) = 4
    

Working memory or RAM

  • Working memory has linear addressing
  • Every byte has an address usually presented in hexadecimal form, e.g. 0x7fffb7335fdc
  • Any address can be accessed at random
  • Pointer is a type to store memory addresses

Pointer

  • * defines a pointer to type
  • The pointers have a type
  • Pointer * can point only to a variable of type
  • Uninitialized pointers point to a random address nullptr

Example:

int* a = nullptr;
double* b = nullptr;
YourType* c = nullptr;

Non-owning pointers

  • Memory pointed to by a raw pointer is not removed when pointer goes out of scope
  • Pointers can either own memory or not
  • Owning memory means being responsible for its cleanup
  • Raw pointers should never own memory
  • We will talk about smart pointers that own memory later

Address operator for pointers

  • Operator & returns the address of the variable in memory
  • Return value type is ‘‘pointer to value type’’

Example:

int a = 42;
int* a_ptr = &a;

Pointer to pointer

Example:

int a = 42;
int* a_ptr = &a;
int** a_ptr_prt = &a_prt;

Pointer dereferencing

  • Operator * returns the value of the variable to which the pointer points
  • Dereferencing of nullptr: Segmentation Fault
  • Dereferencing of unitialized pointer: Undefined Behavior
#include <iostream>
using std::cout; using std::endl;
int main () {
  int a = 42;
  int* a_ptr = &a; //a_ptr pointing address of &a
  int b = * a_ptr; // b variable copy a a_ptr pointer which is pointing a address of &a
  cout << "a = " << a << " b = " << b << endl;
  * a_ptr = 13; //change value of a_ptr which is pointing a address of a
  cout << "a = " << a << " b = " << b << endl;
  return 0;
}
  • output : a = 42, b = 42 a = 13, b = 42

Uninitialized pointer

#include <iostream>
using std::cout;
using std::endl;
int main () {
  int* i_ptr; // BAD! Never leave unitialized!
  cout << "ptr address: " << i_ptr << endl;
  cout << "value under ptr: " << * i_ptr << endl;
  i_ptr = nullptr;
  cout << "new ptr address: " << i_ptr << endl;
  cout << "ptr size: " << sizeof(i_ptr) << " bytes";
  cout << " (" << sizeof(i_ptr) * 8 << "bit) " << endl;
  return 0;
}
  • result
    ptr address: 0x400830
    value under ptr: -1991643855
    new ptr address: 0
    ptr size: 8 bytes (64 bit)
    

Important of Point

  • Always initialize with a value or a nullptr
  • Dereferencing a nullptr causes a Segmentation Fault
  • Use if to avoid Segmentation Faults
    if(some_ptr ) {
    // only enters if some_ptr != nullptr
    }
    if(! some_ptr) {
    // only enters if some_ptr == nullptr
    }
    

Arrays in memory and pointers

  • Array elements are continuous in memory
  • Name of an array is an alias to a pointer:
    double ar [3];
    double* ar_ptr = ar;
    double* ar_ptr = &ar [0];
    
  • Get array elements with operator []

Careful! Overflow!

#include <iostream>
int main () {
  int ar[] = {1, 2, 3};
  // WARNING! Iterating too far!
  for (int i = 0; i < 6; i++){
    std :: cout << i << ": value: " << ar[i]
                << "\t addr:" << &ar[i] << std :: endl;
  }
  return 0;
}
  • result
    0: value: 1 addr :0 x7ffd17deb4e0
    1: value: 2 addr :0 x7ffd17deb4e4
    2: value: 3 addr :0 x7ffd17deb4e8
    3: value: 0 addr :0 x7ffd17deb4ec
    4: value: 4196992 addr :0 x7ffd17deb4f0
    5: value: 32764 addr :0 x7ffd17deb4f4
    
  • int 4 bytes(8bits * 4 = 32bits )
  • float 8 bytes(8bits * 8 = 64 bits)
  • char 1 bytes(8 bits * 1 = 8 bits)
  • double 16 bytes(8bits * 16 )

Custom objects in memory

  • How the parts of an object are stored in memory is not strongly defined
  • Usually sequentially
  • The compiler can optimize memory
class MemoryTester {
  public:
    int i;
    double d;
    void SetData(float data) { data_ = data;}
    float* GetDataAddress() { return &data_;}
  private:
    float data_;

Where is what

#include "class_memory.h"
#include <iostream>
using std :: cout; using std :: endl;
int main(){
  MemoryTester tester;
  tester.i = 1; tester.d = 2; tester.SetData (3);
  cout << "Sizeof tester: " << sizeof(tester) << endl;
  cout << "Address of i: " << &tester.i << endl;
  cout << "Address of d: " << &tester.d << endl;
  cout << "Address of_data: "
       << tester. GetDataAddress () << endl;
  return 0;
}
//output
// memory: |i|i|i|i|_|_|_|_|d|d|d|d|d|d|d|d|...
// who is who: | int i |padding| double d |...

Reference

https://www.ipb.uni-bonn.de/teaching/modern-cpp/

Cpp Core Guidelines: https://github.com/isocpp/CppCoreGuidelines

Git guide: http://rogerdudler.github.io/git-guide/

C++ Tutorial: http://www.cplusplus.com/doc/tutorial/

Book: Code Complete 2 by Steve McConnell

Modern CMake Tutorial https://www.youtube.com/watch?v=eC9-iRN2b04

Compiler Explorer: https://godbolt.org/ Gdbgui: https://www.gdbgui.com/ CMake website: https://cmake.org/ Gdbgui tutorial: https://www.youtube.com/watch?v=em842geJhfk

Fluent C++: structs vs classes: https://google.github.io/styleguide/cppguide.html#Structs_vs._Classes

Comment  Read more

6. polymorphism, I/O, String streams, Cmake find_package

|

Polymorphism(something take many shape)

  • Allows morphing(变形) derived classes into their base class type:
    • const Base& base = Derived(…)
    • there are relationship between A and B

When is it useful?

  • Allows encapsulating the implementation inside a class only asking it to conform to a common interface
  • 즉 이미지 프로세싱에 있어 두가징 방법으로 같은 결과 구함(이미지 한장가지고 여러 가지 일을 함 : Canny detection , bitwise )
  • Often used for:
    • Working with all children of some Base class in unified(统一) manner
    • Enforcing an interface in multiple classes to force them to implement some functionality
    • In strategy pattern, where some complex functionality is outsourced into separate classes and is passed to the object in a modular fashion
  • image class we want to save two difference processed picture.

Creating a class hierarchy

  • Sometimes classes must form a hierarchy
  • Distinguish between is a and has a to test if the classes should be in one hierarchy:
    • Square is a Shape: can inherit from Shape
    • Student is a Human: can inherit from Human
    • Car has a Wheel: should not inherit each other
  • Prefer shallow hierarchies
  • GOOGLE-STYLE Prefer composition, i.e. including an object of another class as a member of your class

Example 1

#include <iostream>
using std::cout; using std::endl;
class Rect{
public:
  Rect(int w, int h) : w_{w}, h_{h} {}
  virtual void Print() const {
    cout  << "Rect: " << w_ << "x" <<h_ <<endl;
  }
private:
  int w_ = 0;
  int h_ = 0;
};
struct Square : public Rect{
  explicit Square(int size) : Rect{size,size} {}
  void Print() const override {
    cout << "Square:" <<w_<<"x"<<h_<<end; }
};
void Print(const Rect& rect) { rect.Print()}
int main(){
  Print(Square(10)); Print(Rect(10,20));
  return 0;
}

Example 2

#include <iostream>
using std::cout; using std::endl;
class Rect{
public:
  Rect(int w, int h) : w_{w}, h_{h} {}
  void Print(){
    cout  << "Rect: " << w_ << "x" <<h_ <<endl;
  }
private:
  int w_ = 0;
  int h_ = 0;
};
struct Square : public Rect{ // abstract functionality
  explicit Square(int size) : Rect{size,size} {}
};
int main(){
  Square square(10);
  square.Print();
  return 0;
}

Example 3

#include <iostream>
using std::cout; using std::endl;
class Rect{
public:
  Rect(int w, int h) : w_{w}, h_{h} {}
  virtual void Print() const {
    cout  << "Rect: " << w_ << "x" <<h_ <<endl;
  }
private:
  int w_ = 0;
  int h_ = 0;
};
class Square : public Rect{
public:
  explicit Square(int size) : Rect{size,size} {} // 부모 개열의 데이터 베리어블을 끌어와 쓴다.
  void Print() const override {
    cout << "Square:" <<w_<<"x"<<h_<<end; }
};
int main(){
  Square square(10);
  const Rect& rect_ref = square;
  rect_ref.Print();
  return 0;
}
// output square 10x10

Using interfaces

  • 모든 instance가 연결되어 있다.
  • Use interfaces when you must enforce other classes to implement some functionality
  • Allow thinking about classes in terms of abstract functionality(it has at laset pure virutal function but no implementation)
  • Hide implementation from the caller
  • Allow to easily extend functionality by simply adding a new class
#include <iostream>
using std::cout;
using std::endl;
struct Printable{
  virtual void Print() const = 0; //Abstract functionlity: it has at least pure virutal function but no implementation
};
struct A : public Printable{
  void Print() const override{cout << "A" << endl;}
};
struct B : public Printable{
  void Print() const override{cout << "B" << endl;}
};
void Print(const Printable& val) {var.Print()}
int main(){
  Print(A());
  Print(B());
  return 0;
}

Using strategy pattern

  • If a class relies on complex external, functionality use strategy pattern
  • Allows to add/switch functionality of the class without changing its implementation
  • All strategies must conform to one strategy interface
    #include <iostream>
    using std::cout; using std::endl;
    struct Strategy { // Saving space , should be classes.
    virtual void Print () const = 0;
    };
    struct StrategyA : public Strategy{
    void Print() const override {cout <<"A"<<endl;}
    };
    struct StrategyB : public Strategy{
    void Print() const override {cout <<"B"<<endl;}
    };
    struct MyStruct{
    MyStruct(const Strategy& s): strategy_(s) {}
    void Print () const { strategy_ .Print (); }
    const Strategy& strategy_;
    }
    int main () {
    // Create a local var of MyStruct and call its Print
    MyStruct(StrategyA()).Print ();
    MyStruct(StrategyB()).Print ();
    }
    

Do not overuse it

  • Only use these patterns when you need to
  • If your class should have a single method for some functionality and will never need another implementation don’t make it virtual
  • Used mostly to avoid copying code and to make classes smaller by moving some functionality out

Reading and writing to files

  • Use streams from STL
  • Syntax similar to cerr, cout
#include <fstream>
using std::string;
using Mode = std::ios_base::openmode;
// ifstream: stream for input from file
std :: ifstream f_in(string& file_name , Mode mode);
// ofstream: stream for output to fil
std :: ofstream f_out(string& file_name , Mode mode);
// stream for input and output to file
std :: fstream f_in_out(string& file_name , Mode mode);

There are many modes under which a file can be opened

Regular columns

Use it when:

  • The file constains organized data
  • Every line has to have all columns

Reading from ifstream

#include <fstream> // For the file streams.
#include <iostream>
#include <string>
using namespace std; // Saving space.
int main () {
  int i;
  double a, b;
  string s;
  ifstream in("test_cols.txt", ios_base::in);
  // Read data , until it is there.
  while (in >> i >> a >> s >> b) {
    cerr << i << ", " << a << ", "  
         << s << ", " << b << endl;
       }// print out from text file
  return 0;
}

Reading files one line at a time

  • Bind every line to a string
  • Afterwards parse the string

#include <fstream> // For the file streams.
#include <iostream>
using namespace std; // Saving space.
int main () {
  string line, file_name;
  ifstream input("test_bel.txt", ios_base::in);
  // Read data line -wise
  while (getline(input , line)) {
    cout << "Read: " << line << endl;
    // String has a find method
    string :: size_type loc = line.find("filename", 0);
    if (loc != string :: npos) {
      file_name = line.substr(line.find("=", 0) + 1,string :: npos);
       }
     }
  cout << "Filename found: " << file_name << endl;
  return 0;
}

Writing into text files

  • With the same syntax as cerr and cout streams, with ofstream we can write directly into files
    #include <iomanip> // For setprecision.
    #include <fstream>
    using namespace std;
    int main(){
    string filename = "out.txt";
    ofstream outfile(filename);
    if (!outfile.is_open ()) { return EXIT_FAILURE ; }
    double a = 1.123123123;
    outfile << "Just string" << endl; // write in the file directly
    outfile << setprecision (20) << a << endl;
    return 0;
    }
    

String streams

Already known streams:

  • Standard output: cerr, cout
  • Standard input: cin
  • Filestreams: fstream, ifstream, ofstream

    New type of stream: stringstream

  • Combine int, double, string, etc. into a single string
  • Break up strings into int, double, string etc.
    #include <iomanip>
    #include <iostream>
    #include <sstream>
    using namespace std;
    int main()
    {
      stringstream s_out;
      string ext = ".txt", file_name = "";
      for (int i = 0; i < 500; ++i) {
    // Combine variables into a stringstream.
    s_out << setw (5) << setfill('0') << i << ext;
    file_name = s_out.str (); // Get a string.
    s_out.str(""); // Empty stream for next iteration.
    cerr << file_name << endl;
      }
      stringstream s_in( file_name );
      int i; string rest;
      s_in >> i >> rest;
      cerr << "Number: " << i << " rest is: " << rest;
      return 0;
    }
    

CMake find_path and find_library

  • We can use an external library
  • Need headers and binary library files
  • There is an easy way to find them

Headers:

find_path(SOME_PKG_INCLUDE_DIR include/some_file .h
          <path1 > <path2 > ...)
include_directories(${SOME_PKG_INCLUDE_DIR})

Libraries:

find_library(SOME_LIB
            NAMES <some_lib >
            PATHS <path1 > <path2 > ...)
target_link_libraries(target ${SOME_LIB})

find_package

  • find_package calls multiple find_path and find_library functions
  • To use find_package() CMake must have a file Find.cmake in MAKE_MODULE_PATH folders
  • Find.cmake defines which libraries and headers belong to package
  • Pre-defined for most popular libraries, e.g. OpenCV, libpng, etc.

CMakeLists.txt

1 cmake_minimum_required(VERSION 2.8)
2 project( first_project )
3
4 # CMake will search here for Find <pkg >.cmake files
5 SET( CMAKE_MODULE_PATH
6 ${PROJECT_SOURCE_DIR}/ cmake_modules )
7
8 # Search for Findsome_pkg.cmake file and load it
9 find_package(some_pkg )
10
11 # Add the include folders from some_pkg
12 include_directories(${some_pkg_INCLUDE_DIRS})
13
14 # Add the executable "main"
15 add_executable(main small_main .cpp)
16 # Tell the linker to bind these binary objects
17 target_link_libraries(main ${some_pkg_LIBRARIES})

cmake_modules/Findsome_pkg.cmake

1 # Find the headers that we will need
2 find_path( some_pkg_INCLUDE_DIRS include/some_lib.h
3 <FOLDER_WHERE_TO_SEARCH >)
4 message(STATUS "headers: ${some_pkg_INCLUDE_DIRS}")
5
6 # Find the corresponding libraries
7 find_library( some_pkg_LIBRARIES
8 NAMES some_lib_name
9 PATHS <FOLDER_WHERE_TO_SEARCH >)
10 message(STATUS "libs: ${some_pkg_LIBRARIES}")

Reference

https://www.ipb.uni-bonn.de/teaching/modern-cpp/

Cpp Core Guidelines: https://github.com/isocpp/CppCoreGuidelines

Git guide: http://rogerdudler.github.io/git-guide/

C++ Tutorial: http://www.cplusplus.com/doc/tutorial/

Book: Code Complete 2 by Steve McConnell

Modern CMake Tutorial https://www.youtube.com/watch?v=eC9-iRN2b04

Compiler Explorer: https://godbolt.org/ Gdbgui: https://www.gdbgui.com/ CMake website: https://cmake.org/ Gdbgui tutorial: https://www.youtube.com/watch?v=em842geJhfk

Fluent C++: structs vs classes: https://google.github.io/styleguide/cppguide.html#Structs_vs._Classes

Comment  Read more

5. Move semantics, Classes

|

intuition lvalues, rvalues

  • Every expression is an lvalue or an rvalue
  • lvalues can be written on the left of assignment operator (=)
  • rvalues are all the other expressions
  • Explicit rvalue defined using &&
  • Use std::move(…) to explicitly convert an
  • lvalue to an rvalue
    int a; // "a" is an lvalue
    int& a_ref = a; // "a" is an lvalue
                  // "a_ref" is a reference to an lvalue
    a = 2 + 2; // "a" is an lvalue ,
            // "2 + 2" is an rvalue
    int b = a + 2; // "b" is an lvalue , "a + 2" is an rvalue
    int&& c = std :: move(a); // "c" is an rvalue
    

Hands on example

#include <iostream>
#include <string>
using namespace std; // Save space on slides.
void Print(const string& str) {
  cout << "lvalue: " << str << endl;
}
void Print(string && str) {
  cout << "rvalue: " << str << endl;
}
int main () {
  string hello = "hi";
  Print(hello); // lvalue
  Print("world"); // lvalue
  Print(std :: move(hello)); // rvalue : more save memory and time
  // DO NOT access "hello" after move!
  return 0;
}

Never access values after move

  • 즉 Move를 하면 해당 variable에 value는 NULL이 되어 찾을 수 없다.
  • The value after move is undefined
#include <iostream>
#include <string>
#include <vector>
using namespace std; // Save space on slides.
int main () {
  string hello = "hello";
  vector <string > owner;
  owner. emplace_back (hello); // Copy.
  owner. emplace_back (move(hello)); // Move.
  cout << hello << endl; // Undefined.
  return 0;
}

How to think about std::move

  • Think about ownership
  • Entity(독립체) owns a variable if it deletes it, e.g.
    • A function scope owns a variable defined in it
    • An object of a class owns its data members
  • Moving a variable transfers ownership of its resources to another variable
  • When designing your program think “who should own this thing?”
  • Runtime: better than copying, worse than passing by reference

Custom operators for a class

  • Operators are functions with a signature:
    • operator()
  • represents the target operation, e.g. >, <, =, ==, << etc.
  • Have all attributes of functions
  • Always contain word operator in name
  • All available operators: http://en.cppreference.com/w/cpp/language/operators

Example operator <

#include <algorithm >
#include <vector >
using namespace std;
class Human{
public:
  Human(int kindness) : kindness_{kindness}{}
  bool operator <(const Human& other) const {
    return kindness_ < other. kindness_ ;
  }
private:
  int kindness_ = 100;
};
int main () {
  vector <Human > humans = {Human {0}, Human {10}};
  std :: sort(humans.begin (), humans.end ());
  return 0;
}

Copy constructor

  • Called automatically when the object is copied
  • For a class MyClass has the signature: MyClass(const MyClass& other)
MyClass a; // Calling default constructor.
MyClass b(a); // Calling copy constructor.
MyClass c = a; // Calling copy constructor.

Copy assignment operator

  • Copy assignment operator is called automatically when the object is assigned a new value from an Lvalue
  • For class MyClass has a signature: MyClass& operator=(const MyClass& other)
  • Returns a reference to the changed object
  • Use * this from within a function of a class to get a reference to the current object
MyClass a; // Calling default constructor.
MyClass b(a); // Calling copy constructor.
MyClass c = a; // Calling copy constructor.
a = b; // Calling copy assignment operator.

Move constructor

  • Called automatically when the object is moved
  • For a class MyClass has a signature: MyClass(MyClass&& other)
MyClass a; // Default constructors.
MyClass b(std :: move(a)); // Move constructor
MyClass c = std :: move(a); // Move constructor.

Move assignment operator

  • Called automatically when the object is assigned a new value from an Rvalue
  • For class MyClass has a signature: MyClass& operator=(MyClass&& other)
  • Returns a reference to the changed object
MyClass a; // Default constructors.
MyClass b(std :: move(a)); // Move constructor.
MyClass c = std :: move(a); // Move constructor.
b = std :: move(c); // Move assignment operator.

Example of Assignment operator

#include <iostream >
using std :: cout; using std :: endl;
class Hmm{
public:
  Hmm(){cout<<"default"<<endl;}
  Hmm(const Hmm& other){cout <<"copy"<<endl;}
  Hmm(Hmm&& other) {cout<<"move"<<endl;}
  Hmm& operator=(const Hmm& other){ //copy assignment operator
    cout<<"copy operator"<<endl; return * this;
  }
  Hmm& operator=(Hmm&& other){
    cout << "move operator"<<endl; return * this;
  }
}
int main () {
  Hmm a; // default constructor
  Hmm b = a; // copy contructor
  a = b; // copy operator
  Hmm c = std :: move(a); // move contructor
  c = std :: move(b); // move operator
  return 0;
}
  • 파라미터가 Class data 타입일 떄 항상 function 파라미터에 Reference & 를 붙인다.
  • Move일 경우에는 && Refenrence 두개를 붙인다.
  • copy operator일 경우에는 datatype& operator=(datatype&& other)이렇게 한다.

Do I need to define all of them?

  • The constructors and operators will be generated automatically
  • Under some conditions…
  • Five special functions for class MyClass:
    • ~MyClass()
    • MyClass(const MyClass& other)
    • MyClass(MyClass&& other)
    • MyClass& operator=(const MyClass& other)
    • MyClass& operator=(MyClass&& other)
  • None of them defined: all autogenerated
  • Any of them defined: none autogenerated

Rule of all or nothing

  • Try to define none of the special functions
  • If you must define one of them define all
  • Use =default to use default implementation
class MyClass {
  public:
    MyClass () = default;
    MyClass(MyClass && var) = default;
    MyClass(const MyClass& var) = default;
    MyClass& operator=( MyClass && var) = default;
    MyClass& operator=(const MyClass& var) = default;
};

Deleted functions

  • Any function can be set as deleted
    void SomeFunc (...) = delete;
    
  • Calling such a function will result in compilation error
  • Example: remove copy constructors when only one instance of the class must be guaranteed
  • Compiler marks some functions deleted automatically
  • Example: if a class has a constant data member, the copy/move constructors and assignment operators are implicitly deleted

Inheritance

  • Classes and structs can inherit data and functions from other classes
  • There are 3 types of inheritance in C++:
    • public [used in this course] GOOGLE-STYLE
    • protected
    • private
  • public inheritance keeps all access specifiers of the base class

Public inheritance

  • Public inheritance stands for “is a” relationship, i.e. if class Derived inherits publicly from class Base we say, that Derived is a kind of Base
    class Derived:public Base{
    // Contents of the derived class
    };
    };
    
  • Allows Derived to use all public and protected members of Base
  • Derived still gets its own special functions: constructors, destructor, assignment operators

Example 1 (inheritance)

#include <iostream>
using std::cout; using std::endl;
class Recatangle{
public:
  Recatangle(int width, int height) : width_{width}, height_(height)
  int weight() const {return width_;}
  int height() const {return height_;}
private:
  int width_=0;
  int height_=0;
};
class Square : public Recatangle{
public:
  explicit Square(int size) : Recatangle{size,size}{};
  // explicit is google style, inheritance가 있고 children일떄 사용한다.
};
int main(){
  Square sq(10); //short name to save space
  cout << sq.width()<< "" <<sq.height()<<endl;
  return 0;
}

Example 2

#include<iostream>
#include<string>
using std::cout; using std::endl;
class Printer{
public:
  Printer(const std::string& name){name_=name}
  void printer(){cout<<name_<<endl;}
private:
  std:string name_;
}
int main()
{
  std::string hello = "hi";
  Printer printer(hello)
  printer.Print()
  return 0;
}

Function overriding

  • A function can be declared virtual
    virtual Func(<PARAMS >); // assume in base class
    
  • If function is virtual in Base class it can be overridden in Derived class:
    Func(<PARAMS>) override; // assume in derived class
    
  • Base can force all Derived classes to override a function by making it pure virtual
    virtual Func(<PARAMS >) = 0;
    

Overloading vs overriding

  • Do not confuse function overloading and overriding
  • Overloading:
    • Pick from all functions with the same name, but different parameters
    • Pick a function at compile time
    • Functions don’t have to be in a class
  • Overriding:
    • Pick from functions with the same arguments and names in different classes of one class hierarchy
    • Pick at runtime

Abstract classes and interfaces

  • Abstract class: class that has at least one pure virtual function
  • Interface: class that has only pure virtual functions and no data members

How virtual works

  • A class with virtual functions has a virtual table
  • When calling a function the class checks which of the virtual functions that match the signature should be called
  • Called runtime polymorphism(多型现象)
  • Costs some time but is very convenient

Reference

https://www.ipb.uni-bonn.de/teaching/modern-cpp/

Cpp Core Guidelines: https://github.com/isocpp/CppCoreGuidelines

Git guide: http://rogerdudler.github.io/git-guide/

C++ Tutorial: http://www.cplusplus.com/doc/tutorial/

Book: Code Complete 2 by Steve McConnell

Modern CMake Tutorial https://www.youtube.com/watch?v=eC9-iRN2b04

Compiler Explorer: https://godbolt.org/ Gdbgui: https://www.gdbgui.com/ CMake website: https://cmake.org/ Gdbgui tutorial: https://www.youtube.com/watch?v=em842geJhfk

Fluent C++: structs vs classes: https://google.github.io/styleguide/cppguide.html#Structs_vs._Classes

Comment  Read more