Quincas Borba


Modern C++: Getting Started

Some assumptions:


Getting started with Makefile

Makefiles are a way to configure your build setup, powerful and simple to use.

A very simple example to compile a ./src/main.cpp file with a cpp program into a ./build/main.o binary file:

CXX=g++

SRC_DIR=./src
SRC=$(SRC_DIR)/main.cpp

OUT_DIR=./build
OUT=$(OUT_DIR)/main.o

build:
  mkdir -p $(OUT_DIR)
  $(CXX) $(SRC) -o $(OUT)

clean:
  rm -rf $(OUT_DIR)

You can define alias to values like, files, folders, programs just like this:

ALIAS=value

To define command alias, you can do like this:

command:
  echo "some custom command!"

To combine those you can do like this:

ALIAS="some custom command!"

command:
  echo $(ALIAS)

and this is the basic Makefile knowledge you need to get started.


Getting started with modern C++

In this post I'll talk about C++ different ways to pass parameters, classes and smart pointers, show an simple example and then talk about those concepts.

A simple program to implement an inverse linked list:

// main.cpp
#include <iostream>
#include <memory>

class Node {
public:
  int value;
  std::shared_ptr<Node> next;

  Node(int value, const std::shared_ptr<Node>& next)
    : value(value), next(next) {
    std::cout << "Creating: " << this->value << std::endl;
  }

  ~Node() {
    std::cout << "Destroying: " << this->value << std::endl;
  }
};

class LinkedList {
public:
  std::shared_ptr<Node> head = nullptr;

  void add_node(int value) {
    this->head = std::make_shared<Node>(value, this->head);
  }

  void print() {
    std::shared_ptr<Node> tmp = this->head;

    while (tmp != nullptr) {
      std::cout << tmp.get() << ": " << tmp->value << std::endl;
      tmp = tmp->next;
    }
  }
};

int main() {
  LinkedList linked_list;
  linked_list.add_node(1);
  linked_list.add_node(2);
  linked_list.add_node(3);
  linked_list.print();
  return 0;
}

The program output:

Creating: 1
Creating: 2
Creating: 3
0x55effa696330: 3
0x55effa696300: 2
0x55effa695ec0: 1
Destroying: 3
Destroying: 2
Destroying: 1

Explaning used concepts

Passing parameters:

Passing by value:

This type of parameter make a copy of the passed value to the function scope, do not change the passed variable.

void func(T);

Passing by reference (alias):

This type pass a reference of the passed value to the function scope, so, if you change the value, the passed variable you change too.

void func(int&);

int a = 1;

func(a);

Also can be passed by pointer and the variable can be also changed, but you have to defer the pointer first:

void func(int*);

int a = 1;

func(&a);

void func(int* value) {
  std::cout << *value << std::endl;
}

Passing by constant reference:

This type pass a reference of the passed value to the function scope, but you can change this value.

void func(const int&);

int a = 1;

func(a);
Classes:

I like classes, do you like classes too?

Here is a simple example of a class in cpp, with a constructor, a destructor and operator overloading.

// main.cpp
#include <iostream>

class Vec2 {
public:
  int x, y;

  Vec2(int x, int y) : x(x), y(y) {}
  ~Vec2() {}

  Vec2 operator+(const Vec2& vec) {
    return Vec2(this->x + vec.x, this->y + vec.y);
  }
};

int main() {
  Vec2 vec_a(1, 2);
  Vec2 vec_b(3, 4);

  Vec2 another_vec = vec_a + vec_b;

  std::cout << another_vec.x << ", " << another_vec.y << std::endl;

  return 0;
}

The program output:

4, 6

Constructor:

They have two ways to instanciate:

MyClass instance; // on the stack
MyClass* instance = new MyClass; // on the heap

You can use the smart pointers constructors helpers to instanciate classes too, they will be located on the heap.

Destructor:

Operator overload:

In classes you have "data" and "actions" in the same place, and also have different type of access for that (public, protected, private), so you can make nice abstractions with it.

Smart pointers:

Smart pointers are basically auto managed pointers, wrapper's to raw pointers. You can access they in the standard library by including memory header.

NOTE: if you can use reference instead of a pointer, use the reference.

Shared pointer:

std::shared_ptr<T> my_ptr;

Constructor helper:

std::shared_ptr<T> my_ptr = std::make_shared<T>();

The std::shared_ptr is a smart pointer that retains shared ownership of an object through a pointer.

The object is destroyed using delete-expression or a custom deleter that is supplied to shared_ptr during construction.

Unique pointer:

std::unique_ptr<T> my_ptr;

Constructor helper:

std::unique_ptr<T> my_ptr = std::make_shared<T>();

The std::unique_ptr is a smart pointer that owns and manages another object through a pointer and disposes of that object when the unique_ptr goes out of scope.

Weak pointer:

std::weak_ptr<T> my_ptr;

There's no constructor helper to weak pointers.

The weak pointer is a smart pointer that holds a non-owning ("weak") reference to an object that is managed by std::shared_ptr. It must be converted to std::shared_ptr in order to access the referenced object.

Smart pointer descriptions source: cppreference.com/w/cpp/memory. To more info about those concepts, check it out the best C++ docs: cppreference.com.


Book recomendations:

A fast way to start a C++ project:

You can use my boilerplate: CPP-Boilerplate.

git clone https://github.com/Raisess/cpp-boilerplate my-cpp-project

Change CXX to your desired compiler, default is g++.

And that's all, see ya!


Your assumptions are your windows on the world. Scrub them off every once in a while, or the light won't come in.

Isaac Asimov