# Modern C++: Stop Writing 'C with Classes'

At the first year of University they taught me C and later on C++. While it is a hard language to learn as the first one, it has an incredible advantage over some others. **It teaches you to think about dealing with memory**. *But you already knew that…*

The problem? Most universities and (online tutorials) teach you C++ as if it were “C with classes tacked on top”. You learn **new** and **delete,** raw pointers everywhere, manual memory management… Basically C with some extra facilities.

> *And that’s where things get dangerous.*

And by teaching you this they are not doing anything wrong, **learning this is fundamental;** but, C++ is constantly evolving and **safety-practices are also.**

---

## The “C with Classes” Trap

A quick example about what I mean:

```cpp
class Buffer {
    char* data;
    int size;

public:
    Buffer(int s) {
        size = s;
        data = new char[size];
    }
    
    ~Buffer() {
        delete[] data;
    }
    
    char* getData() { return data; }
};
```

It doesn’t look wrong, right? There are classes, encapsulation, a destructor…

**Well, actually, this is a minefield:**

1. **No copy constructor:** Without a copy constructor, copying this object leads to undefined behavior, most likely a [double free.](https://www.delftstack.com/howto/cpp/double-free-or-corruption-cpp/)
    
2. **No move constructor:** More inefficient.
    
3. **Raw pointer exposed:** With “char\* getData()…” you are exposing a raw pointer to the internal memory. This means external code has full control over the buffer.
    
4. **No bounds checking:** Potential [buffer overflow.](https://en.wikipedia.org/wiki/Buffer_overflow)
    
5. **Manual memory management:** Easier having a [memory leak.](https://www.tutorialspoint.com/cprogramming/c_memory_leaks.htm)
    

This is “C with classes.” You’re using class syntax but **still thinking in C**. And in reality this stuff gets exploited **constantly**.

---

## The Modern Way (C++ 11 and Up)

```cpp
class Buffer {
    std::vector<char> data;
    
public:
    Buffer(size_t size) : data(size) {}
    
    std::span<char> getData() { return data; }
    char& at(size_t pos) { return data.at(pos); }
};
```

Now:

* **No manual memory management** → std::vector handles it.
    
* **Copy/move automatically generated** → compiler does it right.
    
* **Bounds checking** → now the “.at()” throws an exception on overflow.
    
* **Exception safe** → cleanup happens automatically.
    
* **Can’t leak memory** → impossible by how it’s designed.
    

> *The modern version is shorter and safer.*

---

## Core Modern C++ Concepts You Need

C++ has some concepts that are important to get right.

### 1\. RAII (Resource Acquisition Is Initialization)

This is THE fundamental concept in modern C++. Master this, and everything else falls into place.

* Resources → (memory, files, locks) are acquired in constructors.
    
* Resources are released in destructors.
    
* Stack unwinding guarantees cleanup, even during exceptions.
    

Example:

```cpp
void processFile() {
    std::ifstream file(”data.txt”);  // Opens in constructor
    // ... use file ...
    // File closes automatically when scope ends, even if exception thrown
}
```

Now you have no risk about forgetting “fclose()” :).

### 2\. Smart Pointers vs Raw Pointers

What are smart pointers? A smart pointer is a class that wraps a raw pointer and manages its lifetime automatically. That’s it. Check [here](https://en.cppreference.com/book/intro/smart_pointers) for extended documentation.

When to use each type:

* **std::unique\_ptr&lt;T&gt;** → Single owner, the most common. You will be using it most of the time.
    

* **std::shared\_ptr&lt;T&gt;** → Shared ownership via reference counting; use only when ownership must be shared.
    
* **std::weak\_ptr&lt;T&gt;** → Non-owning reference to shared\_ptr (breaks cycles).
    
* **Raw pointers (T\*)** → Only for non-owning references where lifetime is guaranteed externally.
    

Example:

```cpp
class CWindow {
    std::unique_ptr<CTexture> m_pWindowTexture;  // Window owns texture
    CMonitor* m_pMonitor;  // Window doesn’t own monitor, just references it
};
```

*Btw, this code is from Hyprland GitHub repo.*

The pointer type tells you the ownership.

### 3\. std::optional vs Error Codes

Old way (C with classes):

```cpp
int parseConfig(const char* path, Config* out) {
    if (!path) return -1;
    if (!out) return -2;
    // ... parse ...
    return 0;  // Success
}

// Caller can ignore the error.
Config cfg;
parseConfig(”file.ini”, &cfg);  // Ignored return value - BUG
```

Modern way:

```cpp
std::optional<Config> parseConfig(std::string_view path) {
    // ... parse ...
    if (success) {
        return Config{...};
    }
    return std::nullopt;  // Explicit failure
}

// Compiler warns if you don’t check.
auto cfg = parseConfig(”file.ini”);
if (cfg) {
    // Use cfg.value()
}
```

The type system **forces** you to handle failure.

### 4\. **Move Semantics (C++11)**

This is huge for performance without sacrificing safety:

```cpp
std::vector<int> createLargeVector() {
    std::vector<int> v(1000000);
    // ... fill vector ...
    return v;  // Moved, not copied. (C++ 11)
}

auto data = createLargeVector();  // No copy, just a pointer swap
```

Before C++ 11, this would have copied 1 million integers, now there are no copies, the internal buffer is moved.

---

## But Modern C++ Is Slower, right?

Right???

Well **wrong**! This is a common myth on the internet. I guess people see std::unique\_ptr and think “that is an extra overhead compared to raw pointers“.

So what actually happens:

```cpp
// Raw pointer
Widget* ptr = new Widget();
delete ptr;

// Smart pointer
std::unique_ptr<Widget> ptr = std::make_unique<Widget>();
```

And what about the generated assembly?  
To test this I did it with a website called **Compiler Explorer**, [here](https://godbolt.org/z/cz47s31Eq) is the link to see it.

*Used the flag \[-O2 -std=c++20\] for optimizations.*

Modern C++ isn’t just “not slower”, it’s often **faster** than manual memory management:

1. Move semantics avoid copies (we’ve seen this before)
    
2. Compilers optimize STL heavily
    
3. Cache locality std::vector stores elements contiguously.
    

### From the Real World

I’ve been learning about the [Hyprland](https://github.com/hyprwm/Hyprland) codebase, since I want to contribute and make some plugins for my Linux Desktop; and std::unique\_ptr is nearly everywhere.  
Hyprland might not be the best example, but is a good one for demonstrating modern C++.

If smart pointers really had overhead, Hyprland would not be one of the fastest compositors.

### When There IS Overhead

To be fair, there are cases with actual cost:

**std::shared\_ptr has overhead:**

* Reference counting (atomic increments/decrements).
    
* Extra allocation for the control block.
    
* Use only when you actually need shared ownership.
    

**std::function has overhead:**

* Type erasure = virtual call.
    
* Small buffer optimization helps, but it’s there.
    

But std::unique\_ptr has **zero** cost. It’s an Abstraction.

### The Real Cost Are Bugs

Here’s the thing: even if smart pointers had a tiny overhead (they don’t), **bugs are way more expensive.**

The time spent debugging is pretty damn big for fixing it.

* Memory leak → hours tracking it down
    
* Use-after-free → could be days, could be never
    
* Double-free → good luck reproducing it
    
* Time spent with smart pointers = Zero bugs from memory management, or at least close to it.
    

---

## Wrapping Up

If you’re learning C++ today, learn the fundamentals first, then come back to this post and apply these concepts to your previous programs.

**Simple summary:**

* Smart pointers eliminate memory bugs.
    
* RAII guarantees cleanup.
    
* std::optional makes errors explicit.
    
* Move semantics gives you performance.
    

---

**Found this helpful?** Share it with someone learning C++.

Some stuff that is widely recommended for C++:

[Effective Modern C++](https://www.oreilly.com/library/view/effective-modern-c/9781491908419/) by Scott Meyers

[cppreference.com](https://en.cppreference.com/) This is what you will use more.

[Hyprland](https://github.com/hyprwm/Hyprland) source code for some examples I’ve used. (And hyprland is also so cool).

---

I’ll probably be writing more about C++ on this blog while I am learning new concepts.
