Compare

Lang Compare

C++

Systems

Overview

High-performance systems language with zero-cost abstractions. Used for game engines, operating systems, embedded systems, and performance-critical applications.

Resources

Popular learning and reference links:

Installation & Getting Started

Install a C++ compiler (GCC, Clang, or MSVC) and optionally CMake for build management.

# macOS — Clang comes with Xcode Command Line Tools
xcode-select --install
clang++ --version

# Linux (Ubuntu/Debian) — GCC
sudo apt install build-essential cmake
g++ --version

# Linux — Clang
sudo apt install clang

# Windows — MSVC via Visual Studio, or MinGW/MSYS2
# Download from https://visualstudio.microsoft.com/

# Verify
g++ --version    # or clang++ --version
cmake --version
# No built-in REPL — but cling provides one
# Install cling: https://github.com/root-project/cling
cling

# Compiler Explorer — try in the browser
# https://godbolt.org/

# Compile and run
g++ -std=c++20 -o hello hello.cpp && ./hello
clang++ -std=c++20 -o hello hello.cpp && ./hello

# With CMake
cmake -B build && cmake --build build && ./build/app

# Quick compile + run
echo '#include <iostream>
int main() { std::cout << "Hello!\\n"; }' > /tmp/h.cpp && g++ -o /tmp/h /tmp/h.cpp && /tmp/h

Project Scaffolding

Create a CMake project manually or use a tool like cmake --init.

# Basic CMake project
mkdir my-project && cd my-project
mkdir src

cat > CMakeLists.txt << 'EOF'
cmake_minimum_required(VERSION 3.20)
project(MyProject)
set(CMAKE_CXX_STANDARD 20)
add_executable(app src/main.cpp)
EOF

cat > src/main.cpp << 'EOF'
#include <iostream>
int main() {
    std::cout << "Hello, World!\n";
}
EOF

# Build and run
cmake -B build
cmake --build build
./build/app

Package Management

No built-in package manager. vcpkg and Conan are the most popular options.

# Using vcpkg
vcpkg install fmt
vcpkg install boost:x64-linux

# Using Conan
conan install . --build=missing

# CMake FetchContent (download at build time)
# In CMakeLists.txt:
# FetchContent_Declare(fmt
#   GIT_REPOSITORY https://github.com/fmtlib/fmt
#   GIT_TAG 10.1.1)
# FetchContent_MakeAvailable(fmt)

# System package managers
sudo apt install libboost-all-dev  # Debian/Ubuntu
brew install fmt                    # macOS

Tooling & Formatter/Linter

C++ uses clang-format for formatting and clang-tidy for linting. Both are part of the LLVM toolchain.

# clang-format — code formatter
clang-format -i src/*.cpp src/*.h
clang-format --style=file -i main.cpp  # Use .clang-format

# clang-tidy — linter and static analyzer
clang-tidy src/*.cpp -- -std=c++20
clang-tidy --fix src/main.cpp

# cppcheck — additional static analysis
cppcheck --enable=all src/

# include-what-you-use — minimize #includes
iwyu src/main.cpp
# .clang-format
BasedOnStyle: Google
ColumnLimit: 100
IndentWidth: 4
Language: Cpp
Standard: c++20
AllowShortFunctionsOnASingleLine: Inline
# .clang-tidy
Checks: >
  -*,
  bugprone-*,
  modernize-*,
  performance-*,
  readability-*,
  -modernize-use-trailing-return-type
WarningsAsErrors: ''

Build & Compile Model

Ahead-of-time compiled to native binary. C++ compiles through a preprocessor → compiler → assembler → linker pipeline.

# Direct compilation
g++ -std=c++20 -O2 -o myapp main.cpp
clang++ -std=c++20 -O2 -o myapp main.cpp

# With CMake (standard build system)
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
./build/myapp

# Compile steps (explicit)
g++ -c main.cpp -o main.o    # Compile to object file
g++ main.o -o myapp           # Link

# Cross-compilation
x86_64-linux-gnu-g++ -o myapp main.cpp

# Output: native binary, .o object files, .a/.so libraries

Execution model:

  • Source → Preprocessor (#include expansion) → Compiler → Assembly → Object files → Linker → Binary
  • Headers are textually included (slow — modules in C++20 help)
  • Template instantiation happens at compile time (can be slow)
  • No runtime, no GC — manual memory or RAII
  • Link-time optimization (LTO) can significantly improve performance

Libraries & Frameworks

C++ has a vast ecosystem spanning systems, games, embedded, and high-performance computing.

Standard Library - STL, containers, algorithms, iterators, ranges, <format>, <filesystem>

GUI / Graphics - Qt, Dear ImGui, SDL, SFML, wxWidgets, GLFW

Game Engines - Unreal Engine, Godot, Cocos2d-x

Networking - Boost.Asio, cpp-httplib, libcurl, gRPC

Serialization - nlohmann/json, protobuf, cereal, MessagePack

Databases - SQLite (C API), SOCI, libpq, sqlpp11

Build Systems - CMake, Meson, Bazel, xmake

Package Managers - vcpkg, Conan, CPM.cmake

Testing - Google Test, Catch2, Doctest, Boost.Test

Concurrency - std::thread, std::async, Intel TBB, OpenMP

Math / Science - Eigen, Armadillo, BLAS/LAPACK, OpenCV

Embedded - Arduino, ESP-IDF, Zephyr, mbed

Testing

No built-in testing framework. Google Test (gtest) and Catch2 are the most popular. Doctest is a lightweight alternative.

// Google Test
#include <gtest/gtest.h>

TEST(MathTest, Addition) {
    EXPECT_EQ(1 + 2, 3);
}

TEST(MathTest, Negative) {
    EXPECT_EQ(-1 + 1, 0);
    EXPECT_GT(5, 3);
    EXPECT_TRUE(true);
}

// Parameterized tests
class AddTest : public ::testing::TestWithParam<
    std::tuple<int, int, int>> {};

TEST_P(AddTest, AddsCorrectly) {
    auto [a, b, expected] = GetParam();
    EXPECT_EQ(a + b, expected);
}

INSTANTIATE_TEST_SUITE_P(Math, AddTest,
    ::testing::Values(
        std::make_tuple(1, 2, 3),
        std::make_tuple(0, 0, 0)
    ));
// Catch2 — header-only, no macros
#include <catch2/catch_test_macros.hpp>

TEST_CASE("Addition works", "[math]") {
    REQUIRE(1 + 2 == 3);

    SECTION("with negatives") {
        REQUIRE(-1 + 1 == 0);
    }
}
# Run tests (with CMake + CTest)
cmake -B build && cmake --build build
cd build && ctest --output-on-failure

Debugging

GDB and LLDB are the standard debuggers. Compile with -g for debug symbols. Sanitizers catch runtime errors.

# Compile with debug info
g++ -g -O0 -o myapp main.cpp
clang++ -g -O0 -o myapp main.cpp

# GDB
gdb ./myapp
# run                    (start)
# break main.cpp:42      (breakpoint)
# next                   (step over)
# step                   (step into)
# print variable         (inspect)
# backtrace              (call stack)
# watch variable         (data breakpoint)
# info threads           (list threads)
# thread 2               (switch thread)

# LLDB (macOS default)
lldb ./myapp
# Same workflow, slightly different syntax
# b main.cpp:42, r, n, s, p variable, bt
// Print debugging
#include <iostream>
std::cerr << "debug: " << value << std::endl;

// Assertions
#include <cassert>
assert(ptr != nullptr && "pointer must not be null");

// static_assert (compile-time)
static_assert(sizeof(int) == 4, "int must be 4 bytes");
# Sanitizers — catch bugs at runtime
g++ -fsanitize=address -g -o myapp main.cpp    # Memory errors
g++ -fsanitize=undefined -g -o myapp main.cpp  # Undefined behavior
g++ -fsanitize=thread -g -o myapp main.cpp     # Data races

# VS Code — C/C++ extension or CodeLLDB
# CLion (JetBrains) — built-in debugger
# Visual Studio — best Windows C++ debugger

# Valgrind — memory error detection
valgrind --leak-check=full ./myapp

# Core dumps
ulimit -c unlimited
./myapp  # Crashes → generates core file
gdb ./myapp core

Variables

Use auto for type inference or explicit types. const and constexpr for immutability.

// Auto (type inference, C++11)
auto name = std::string("Alice");
auto age = 30;

// Explicit types
int count = 0;
double pi = 3.14159;
std::string label = "hello";

// Constants
const int maxSize = 100;
constexpr int bufferSize = 1024; // compile-time

// References
int x = 10;
int& ref = x; // reference to x

// Pointers
int* ptr = &x;
auto uptr = std::make_unique<int>(42);

// Structured bindings (C++17)
auto [first, second] = std::make_pair(1, 2);

Types

Statically typed with primitives, classes, templates, and smart pointers.

// Primitives
int n = 42;
double f = 3.14;
bool b = true;
char c = 'a';

// Strings
std::string s = "hello";
std::string_view sv = "view"; // non-owning

// Containers
std::vector<int> nums = {1, 2, 3};
std::map<std::string, int> ages = {
    {"Alice", 30}, {"Bob", 25}
};
std::array<int, 3> fixed = {1, 2, 3};

// Structs / Classes
struct Point { int x; int y; };
class User {
public:
    std::string name;
    int age;
};

// Enums
enum class Color { Red, Green, Blue };

// Optional (C++17)
std::optional<int> maybe = 42;
std::optional<int> empty = std::nullopt;

Data Structures

STL containers: vector, map, unordered_map, set, deque, list, array, stack, queue, priority_queue.

#include <vector>
#include <map>
#include <unordered_map>
#include <set>
#include <array>

// vector — dynamic array (most common)
std::vector<int> v = {1, 2, 3};
v.push_back(4);
v.pop_back();
v[0];                    // 1
v.size();                // 3
v.empty();               // false

// array — fixed-size (stack allocated)
std::array<int, 3> arr = {1, 2, 3};

// map — sorted key-value (red-black tree)
std::map<std::string, int> m;
m["alice"] = 95;
m.contains("alice");     // true (C++20)
m.erase("alice");

// unordered_map — hash map (faster lookup)
std::unordered_map<std::string, int> um;
um["bob"] = 87;
auto it = um.find("bob");

// set — sorted unique values
std::set<int> s = {3, 1, 2};
s.insert(4);
s.contains(2);           // true (C++20)
// Iteration
for (auto& [key, val] : m) { /* structured binding */ }
for (const auto& x : v) { /* range-for */ }

// Algorithms
#include <algorithm>
#include <ranges>

std::sort(v.begin(), v.end());
auto it = std::find(v.begin(), v.end(), 2);
bool has = std::ranges::contains(v, 3); // C++23

// Ranges (C++20)
auto evens = v | std::views::filter([](int x) { return x % 2 == 0; });

// stack, queue, priority_queue (adaptors)
#include <stack>
std::stack<int> stk;
stk.push(1);
stk.top();  // 1
stk.pop();

Functions

Functions support overloading, templates, and lambdas.

// Basic function
std::string greet(const std::string& name) {
    return "Hello, " + name + "!";
}

// Default parameters
int power(int base, int exp = 2) {
    return std::pow(base, exp);
}

// Function overloading
int add(int a, int b) { return a + b; }
double add(double a, double b) { return a + b; }

// Template function
template <typename T>
T first(const std::vector<T>& items) {
    return items.front();
}

// Lambda
auto multiply = [](int a, int b) { return a * b; };
auto adder = [offset = 10](int x) { return x + offset; };

// Trailing return type
auto divide(int a, int b) -> double {
    return static_cast<double>(a) / b;
}

Conditionals

if/else, switch, and if with initializer (C++17).

// If / else
if (x > 0) {
    std::cout << "positive\n";
} else if (x == 0) {
    std::cout << "zero\n";
} else {
    std::cout << "negative\n";
}

// Ternary
auto label = (x > 0) ? "positive" : "non-positive";

// If with initializer (C++17)
if (auto it = map.find("key"); it != map.end()) {
    std::cout << it->second;
}

// Switch
switch (color) {
    case Color::Red:   std::cout << "red"; break;
    case Color::Green: std::cout << "green"; break;
    default:           std::cout << "other"; break;
}

// std::variant + std::visit (pattern matching)
std::visit([](auto&& arg) {
    std::cout << arg;
}, myVariant);

Loops

Standard for, range-based for, while, and STL algorithms.

// Classic for loop
for (int i = 0; i < 5; i++) {
    std::cout << i << "\n";
}

// Range-based for (C++11)
std::vector<std::string> items = {"a", "b", "c"};
for (const auto& item : items) {
    std::cout << item << "\n";
}

// While
int n = 0;
while (n < 3) {
    n++;
}

// Do-while
do {
    n--;
} while (n > 0);

// STL algorithms
std::vector<int> nums = {1, 2, 3};
std::for_each(nums.begin(), nums.end(),
    [](int x) { std::cout << x; });

// Ranges (C++20)
auto squares = nums
    | std::views::transform([](int x) { return x * x; });

Generics & Type System

Templates — compile-time code generation. More powerful than generics (Turing-complete). C++20 adds concepts for constraints.

// Function template
template<typename T>
T identity(T value) { return value; }

identity(42);       // T = int
identity("hello");  // T = const char*

// Concepts (C++20) — constrain templates
#include <concepts>

template<typename T>
concept Numeric = std::integral<T> || std::floating_point<T>;

template<Numeric T>
T add(T a, T b) { return a + b; }

// Requires clause
template<typename T>
requires std::totally_ordered<T>
T max(T a, T b) { return a > b ? a : b; }

// Class template
template<typename T>
class Stack {
    std::vector<T> items;
public:
    void push(T item) { items.push_back(std::move(item)); }
    T pop() {
        T item = std::move(items.back());
        items.pop_back();
        return item;
    }
};
// Template specialization
template<typename T>
std::string to_string(T value) { return std::to_string(value); }

template<>
std::string to_string(bool value) { return value ? "true" : "false"; }

// Variadic templates
template<typename... Args>
void print(Args... args) {
    (std::cout << ... << args) << '\n';  // Fold expression
}

// Non-type template parameters (like const generics)
template<int N>
struct Array {
    int data[N];
};
Array<5> arr;

// SFINAE / if constexpr (compile-time branching)
template<typename T>
auto describe(T val) {
    if constexpr (std::is_integral_v<T>) return "integer";
    else if constexpr (std::is_floating_point_v<T>) return "float";
    else return "other";
}

Inheritance & Composition

Supports single and multiple inheritance, virtual functions for polymorphism, and CRTP for static polymorphism.

// Class inheritance
class Animal {
public:
    std::string name;
    Animal(std::string n) : name(std::move(n)) {}
    virtual std::string speak() {
        return name + " makes a sound";
    }
    virtual ~Animal() = default; // virtual destructor
};

class Dog : public Animal {
public:
    using Animal::Animal;
    std::string speak() override {
        return name + " barks";
    }
};

// Abstract class (pure virtual)
class Shape {
public:
    virtual double area() = 0;
    virtual ~Shape() = default;
};

// Multiple inheritance
class Swimmer { public: void swim(); };
class Flyer   { public: void fly(); };
class Duck : public Animal, public Swimmer, public Flyer {
    using Animal::Animal;
};

// Composition (preferred)
class Engine { public: void start(); };
class Car {
    Engine engine; // composed, not inherited
public:
    void start() { engine.start(); }
};

// CRTP (static polymorphism)
template <typename Derived>
class Base {
    void doWork() { static_cast<Derived*>(this)->impl(); }
};

Functional Patterns

Lambdas, <algorithm>, <ranges>, and std::function enable functional patterns. C++20 ranges add composable pipelines.

// Lambdas
auto double_val = [](int x) { return x * 2; };
auto add = [](int a, int b) { return a + b; };

// Capture by value and reference
int offset = 10;
auto adder = [offset](int x) { return x + offset; };
auto inc = [&offset]() { offset++; };

// STL algorithms
std::vector<int> nums = {1, 2, 3, 4, 5};

std::vector<int> squared;
std::transform(nums.begin(), nums.end(),
    std::back_inserter(squared),
    [](int x) { return x * x; });

auto sum = std::accumulate(nums.begin(), nums.end(), 0);

// C++20 Ranges (composable pipelines)
#include <ranges>
auto result = nums
    | std::views::filter([](int x) { return x % 2 == 0; })
    | std::views::transform([](int x) { return x * x; });

// std::function (type-erased callable)
std::function<int(int)> fn = [](int x) { return x + 1; };

// Higher-order function
template <typename F>
auto apply(F fn, int x) { return fn(x); }

// std::optional chaining
std::optional<int> val = 42;
auto doubled = val.transform([](int x) { return x * 2; });

Concurrency

OS threads with std::thread, atomics, and mutexes. C++20 adds coroutines. No built-in async runtime.

#include <thread>
#include <mutex>
#include <future>

// Threads
std::thread t([] {
    std::cout << "Hello from thread\n";
});
t.join();

// Mutex — protect shared state
std::mutex mtx;
{
    std::lock_guard<std::mutex> lock(mtx);
    shared_data++;
}

// async/future — launch async tasks
auto future = std::async(std::launch::async, [] {
    return compute_result();
});
auto result = future.get();  // Blocks until ready
// Atomics — lock-free concurrency
#include <atomic>
std::atomic<int> counter{0};
counter.fetch_add(1);

// Condition variables
std::condition_variable cv;
std::unique_lock<std::mutex> lock(mtx);
cv.wait(lock, [] { return ready; });

// C++20 coroutines (low-level)
#include <coroutine>
// Requires a custom promise_type / awaitable
// Libraries: cppcoro, folly::coro

// Parallel algorithms (C++17)
#include <algorithm>
#include <execution>
std::sort(std::execution::par, v.begin(), v.end());

Modules & Imports

Traditionally uses header files (#include) with a preprocessor. C++20 introduced modules for faster compilation and better encapsulation. Libraries are linked at build time.

// Header includes (traditional)
#include <iostream>       // standard library
#include <vector>
#include "myheader.h"     // project header

// Include guards (in header files)
#ifndef MY_HEADER_H
#define MY_HEADER_H
// declarations...
#endif

// Or pragma once (widely supported)
#pragma once

// Namespaces
namespace mylib {
    void helper();
}
using namespace std; // import all (discouraged)
using std::vector;   // import specific

// C++20 Modules
// math.cppm (module interface)
export module math;
export int add(int a, int b) { return a + b; }

// main.cpp (consumer)
import math;
import <iostream>; // header unit

// Forward declarations
class MyClass; // declare without defining

// Static/shared libraries
// Linked via CMake: target_link_libraries(app mylib)
// Or pkg-config: pkg-config --libs libfoo

Error Handling

Uses try/catch with exceptions, or error codes and std::expected (C++23).

// Try / catch
try {
    throw std::runtime_error("something failed");
} catch (const std::runtime_error& e) {
    std::cerr << "Error: " << e.what() << "\n";
} catch (...) {
    std::cerr << "Unknown error\n";
}

// Custom exception
class AppError : public std::exception {
    std::string msg;
public:
    AppError(const std::string& m) : msg(m) {}
    const char* what() const noexcept override {
        return msg.c_str();
    }
};

// RAII for cleanup (no finally needed)
{
    std::ofstream file("out.txt");
    file << "data";
} // file automatically closed

// std::optional for "no value"
std::optional<int> find(const std::vector<int>& v, int x) {
    auto it = std::find(v.begin(), v.end(), x);
    if (it != v.end()) return *it;
    return std::nullopt;
}

Memory Management

Manual memory management with RAII (Resource Acquisition Is Initialization). Smart pointers automate ownership. No garbage collector.

// RAII — resources tied to object lifetime
{
    std::string s = "hello";  // Allocated
    std::vector<int> v = {1, 2, 3};
} // Destructors called, memory freed automatically

// Smart pointers (prefer over raw new/delete)
#include <memory>

// unique_ptr — sole ownership
auto ptr = std::make_unique<MyClass>(args);
// Automatically deleted when ptr goes out of scope

// shared_ptr — reference counted
auto shared = std::make_shared<MyClass>(args);
auto copy = shared;  // refcount = 2
// Deleted when last shared_ptr is destroyed

// weak_ptr — non-owning reference
std::weak_ptr<MyClass> weak = shared;
if (auto locked = weak.lock()) {
    // Use locked (shared_ptr)
}
// Raw allocation (avoid in modern C++)
int* raw = new int(42);
delete raw;

int* arr = new int[100];
delete[] arr;

// Stack vs heap
int stack_var = 42;              // Stack (automatic)
auto heap_var = new int(42);     // Heap (manual)

// Move semantics — transfer ownership without copying
std::vector<int> v1 = {1, 2, 3};
std::vector<int> v2 = std::move(v1);
// v1 is now empty, v2 owns the data

// Custom RAII wrapper
class FileHandle {
    FILE* fp;
public:
    FileHandle(const char* path) : fp(fopen(path, "r")) {}
    ~FileHandle() { if (fp) fclose(fp); }
    // Delete copy, allow move
    FileHandle(const FileHandle&) = delete;
    FileHandle(FileHandle&& other) : fp(other.fp) { other.fp = nullptr; }
};

Performance Profiling

Use perf (Linux), Instruments (macOS), gprof, Valgrind, and Google Benchmark for micro-benchmarks.

// Google Benchmark
#include <benchmark/benchmark.h>

static void BM_Sort(benchmark::State& state) {
    for (auto _ : state) {
        std::vector<int> v(1000);
        std::iota(v.begin(), v.end(), 0);
        std::ranges::reverse(v);
        std::ranges::sort(v);
    }
}
BENCHMARK(BM_Sort);
BENCHMARK_MAIN();
# perf (Linux) — sampling profiler
perf record -g ./myapp
perf report
perf stat ./myapp         # Quick summary

# Flame graphs
perf script | stackcollapse-perf.pl | flamegraph.pl > flame.svg

# gprof — GNU profiler
g++ -pg -o myapp main.cpp
./myapp
gprof myapp gmon.out > analysis.txt

# Valgrind — memory + cache profiling
valgrind --tool=callgrind ./myapp
kcachegrind callgrind.out.*

valgrind --tool=massif ./myapp    # Heap profiler
ms_print massif.out.*

# AddressSanitizer / MemorySanitizer
g++ -fsanitize=address -g -o myapp main.cpp
./myapp  # Reports memory errors

# Instruments (macOS)
xcrun xctrace record --template 'CPU Profiler' --launch ./myapp

# Quick timing
#include <chrono>
auto start = std::chrono::high_resolution_clock::now();
doWork();
auto end = std::chrono::high_resolution_clock::now();
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(end - start);

Interop

C++ has native C ABI compatibility. Widely used as the “lingua franca” for interop. Bindings exist for most languages.

// extern "C" — expose C-compatible functions
extern "C" {
    int add(int a, int b) { return a + b; }
    double my_sqrt(double x) { return std::sqrt(x); }
}
// Callable from C, Python (ctypes), Rust (FFI), etc.

// Call C libraries from C++
extern "C" {
    #include <sqlite3.h>
}

// dlopen — load shared libraries at runtime
#include <dlfcn.h>
void* handle = dlopen("libplugin.so", RTLD_LAZY);
auto fn = (int(*)(int))dlsym(handle, "compute");
int result = fn(42);
dlclose(handle);
// pybind11 — C++ ↔ Python
#include <pybind11/pybind11.h>
namespace py = pybind11;

int add(int a, int b) { return a + b; }

PYBIND11_MODULE(mymodule, m) {
    m.def("add", &add, "Add two numbers");
}

// Embind — C++ ↔ JavaScript (Emscripten/WASM)
#include <emscripten/bind.h>
EMSCRIPTEN_BINDINGS(my_module) {
    emscripten::function("add", &add);
}

// SWIG — auto-generate bindings for many languages
// swig -python -c++ mylib.i

// System commands
#include <cstdlib>
std::system("ls -la");

Packaging & Distribution

No central package registry. Distribute via vcpkg/Conan, system package managers, or pre-built binaries. CMake is the build standard.

# CMakeLists.txt — install targets for distribution
install(TARGETS mylib
  EXPORT mylib-targets
  LIBRARY DESTINATION lib
  ARCHIVE DESTINATION lib
  RUNTIME DESTINATION bin
  INCLUDES DESTINATION include
)
install(DIRECTORY include/ DESTINATION include)
install(EXPORT mylib-targets
  FILE mylib-config.cmake
  NAMESPACE mylib::
  DESTINATION lib/cmake/mylib
)
# vcpkg — publish a port
# Create: ports/mylib/portfile.cmake + vcpkg.json
# Submit PR to microsoft/vcpkg

# Conan — publish a package
conan create .
conan upload mylib/1.0 --remote=myremote

# CPack — create installers from CMake
cmake -B build -DCMAKE_BUILD_TYPE=Release
cmake --build build
cd build && cpack
# Generates: .deb, .rpm, .tar.gz, .zip, NSIS installer

# Static linking for portable binaries
g++ -static -o myapp main.cpp

# Docker
FROM ubuntu:22.04 AS builder
RUN apt-get update && apt-get install -y build-essential cmake
COPY . /app
WORKDIR /app
RUN cmake -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build

FROM ubuntu:22.04
COPY --from=builder /app/build/myapp /usr/local/bin/
CMD ["myapp"]