<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Sunerk's Blog]]></title><description><![CDATA[Sunerk's Blog]]></description><link>https://blog.sunerk.com</link><generator>RSS for Node</generator><lastBuildDate>Tue, 05 May 2026 07:12:21 GMT</lastBuildDate><atom:link href="https://blog.sunerk.com/rss.xml" rel="self" type="application/rss+xml"/><language><![CDATA[en]]></language><ttl>60</ttl><item><title><![CDATA[Modern C++: Stop Writing 'C with Classes']]></title><description><![CDATA[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…
T...]]></description><link>https://blog.sunerk.com/modern-c-stop-writing-c-with-classes</link><guid isPermaLink="true">https://blog.sunerk.com/modern-c-stop-writing-c-with-classes</guid><category><![CDATA[C++]]></category><category><![CDATA[Modern C++]]></category><category><![CDATA[Security]]></category><dc:creator><![CDATA[Sunerk]]></dc:creator><pubDate>Sun, 02 Nov 2025 16:06:02 GMT</pubDate><enclosure url="https://cdn.hashnode.com/res/hashnode/image/upload/v1762099261592/945876c8-61c7-461f-918f-9b4374560220.jpeg" length="0" type="image/jpeg"/><content:encoded><![CDATA[<p>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. <strong>It teaches you to think about dealing with memory</strong>. <em>But you already knew that…</em></p>
<p>The problem? Most universities and (online tutorials) teach you C++ as if it were “C with classes tacked on top”. You learn <strong>new</strong> and <strong>delete,</strong> raw pointers everywhere, manual memory management… Basically C with some extra facilities.</p>
<blockquote>
<p><em>And that’s where things get dangerous.</em></p>
</blockquote>
<p>And by teaching you this they are not doing anything wrong, <strong>learning this is fundamental;</strong> but, C++ is constantly evolving and <strong>safety-practices are also.</strong></p>
<hr />
<h2 id="heading-the-c-with-classes-trap">The “C with Classes” Trap</h2>
<p>A quick example about what I mean:</p>
<pre><code class="lang-cpp"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Buffer</span> {</span>
    <span class="hljs-keyword">char</span>* data;
    <span class="hljs-keyword">int</span> size;

<span class="hljs-keyword">public</span>:
    Buffer(<span class="hljs-keyword">int</span> s) {
        size = s;
        data = <span class="hljs-keyword">new</span> <span class="hljs-keyword">char</span>[size];
    }

    ~Buffer() {
        <span class="hljs-keyword">delete</span>[] data;
    }

    <span class="hljs-function"><span class="hljs-keyword">char</span>* <span class="hljs-title">getData</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> data; }
};
</code></pre>
<p>It doesn’t look wrong, right? There are classes, encapsulation, a destructor…</p>
<p><strong>Well, actually, this is a minefield:</strong></p>
<ol>
<li><p><strong>No copy constructor:</strong> Without a copy constructor, copying this object leads to undefined behavior, most likely a <a target="_blank" href="https://www.delftstack.com/howto/cpp/double-free-or-corruption-cpp/">double free.</a></p>
</li>
<li><p><strong>No move constructor:</strong> More inefficient.</p>
</li>
<li><p><strong>Raw pointer exposed:</strong> With “char* getData()…” you are exposing a raw pointer to the internal memory. This means external code has full control over the buffer.</p>
</li>
<li><p><strong>No bounds checking:</strong> Potential <a target="_blank" href="https://en.wikipedia.org/wiki/Buffer_overflow">buffer overflow.</a></p>
</li>
<li><p><strong>Manual memory management:</strong> Easier having a <a target="_blank" href="https://www.tutorialspoint.com/cprogramming/c_memory_leaks.htm">memory leak.</a></p>
</li>
</ol>
<p>This is “C with classes.” You’re using class syntax but <strong>still thinking in C</strong>. And in reality this stuff gets exploited <strong>constantly</strong>.</p>
<hr />
<h2 id="heading-the-modern-way-c-11-and-up">The Modern Way (C++ 11 and Up)</h2>
<pre><code class="lang-cpp"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">Buffer</span> {</span>
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">char</span>&gt; data;

<span class="hljs-keyword">public</span>:
    Buffer(<span class="hljs-keyword">size_t</span> size) : data(size) {}

    <span class="hljs-function"><span class="hljs-built_in">std</span>::span&lt;<span class="hljs-keyword">char</span>&gt; <span class="hljs-title">getData</span><span class="hljs-params">()</span> </span>{ <span class="hljs-keyword">return</span> data; }
    <span class="hljs-function"><span class="hljs-keyword">char</span>&amp; <span class="hljs-title">at</span><span class="hljs-params">(<span class="hljs-keyword">size_t</span> pos)</span> </span>{ <span class="hljs-keyword">return</span> data.at(pos); }
};
</code></pre>
<p>Now:</p>
<ul>
<li><p><strong>No manual memory management</strong> → std::vector handles it.</p>
</li>
<li><p><strong>Copy/move automatically generated</strong> → compiler does it right.</p>
</li>
<li><p><strong>Bounds checking</strong> → now the “.at()” throws an exception on overflow.</p>
</li>
<li><p><strong>Exception safe</strong> → cleanup happens automatically.</p>
</li>
<li><p><strong>Can’t leak memory</strong> → impossible by how it’s designed.</p>
</li>
</ul>
<blockquote>
<p><em>The modern version is shorter and safer.</em></p>
</blockquote>
<hr />
<h2 id="heading-core-modern-c-concepts-you-need">Core Modern C++ Concepts You Need</h2>
<p>C++ has some concepts that are important to get right.</p>
<h3 id="heading-1-raii-resource-acquisition-is-initialization">1. RAII (Resource Acquisition Is Initialization)</h3>
<p>This is THE fundamental concept in modern C++. Master this, and everything else falls into place.</p>
<ul>
<li><p>Resources → (memory, files, locks) are acquired in constructors.</p>
</li>
<li><p>Resources are released in destructors.</p>
</li>
<li><p>Stack unwinding guarantees cleanup, even during exceptions.</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">void</span> <span class="hljs-title">processFile</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-function"><span class="hljs-built_in">std</span>::ifstream <span class="hljs-title">file</span><span class="hljs-params">(”data.txt”)</span></span>;  <span class="hljs-comment">// Opens in constructor</span>
    <span class="hljs-comment">// ... use file ...</span>
    <span class="hljs-comment">// File closes automatically when scope ends, even if exception thrown</span>
}
</code></pre>
<p>Now you have no risk about forgetting “fclose()” :).</p>
<h3 id="heading-2-smart-pointers-vs-raw-pointers">2. Smart Pointers vs Raw Pointers</h3>
<p>What are smart pointers? A smart pointer is a class that wraps a raw pointer and manages its lifetime automatically. That’s it. Check <a target="_blank" href="https://en.cppreference.com/book/intro/smart_pointers">here</a> for extended documentation.</p>
<p>When to use each type:</p>
<ul>
<li><strong>std::unique_ptr&lt;T&gt;</strong> → Single owner, the most common. You will be using it most of the time.</li>
</ul>
<ul>
<li><p><strong>std::shared_ptr&lt;T&gt;</strong> → Shared ownership via reference counting; use only when ownership must be shared.</p>
</li>
<li><p><strong>std::weak_ptr&lt;T&gt;</strong> → Non-owning reference to shared_ptr (breaks cycles).</p>
</li>
<li><p><strong>Raw pointers (T*)</strong> → Only for non-owning references where lifetime is guaranteed externally.</p>
</li>
</ul>
<p>Example:</p>
<pre><code class="lang-cpp"><span class="hljs-class"><span class="hljs-keyword">class</span> <span class="hljs-title">CWindow</span> {</span>
    <span class="hljs-built_in">std</span>::<span class="hljs-built_in">unique_ptr</span>&lt;CTexture&gt; m_pWindowTexture;  <span class="hljs-comment">// Window owns texture</span>
    CMonitor* m_pMonitor;  <span class="hljs-comment">// Window doesn’t own monitor, just references it</span>
};
</code></pre>
<p><em>Btw, this code is from Hyprland GitHub repo.</em></p>
<p>The pointer type tells you the ownership.</p>
<h3 id="heading-3-stdoptional-vs-error-codes">3. std::optional vs Error Codes</h3>
<p>Old way (C with classes):</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-keyword">int</span> <span class="hljs-title">parseConfig</span><span class="hljs-params">(<span class="hljs-keyword">const</span> <span class="hljs-keyword">char</span>* path, Config* out)</span> </span>{
    <span class="hljs-keyword">if</span> (!path) <span class="hljs-keyword">return</span> <span class="hljs-number">-1</span>;
    <span class="hljs-keyword">if</span> (!out) <span class="hljs-keyword">return</span> <span class="hljs-number">-2</span>;
    <span class="hljs-comment">// ... parse ...</span>
    <span class="hljs-keyword">return</span> <span class="hljs-number">0</span>;  <span class="hljs-comment">// Success</span>
}

<span class="hljs-comment">// Caller can ignore the error.</span>
Config cfg;
parseConfig(”file.ini”, &amp;cfg);  <span class="hljs-comment">// Ignored return value - BUG</span>
</code></pre>
<p>Modern way:</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-built_in">std</span>::optional&lt;Config&gt; <span class="hljs-title">parseConfig</span><span class="hljs-params">(<span class="hljs-built_in">std</span>::string_view path)</span> </span>{
    <span class="hljs-comment">// ... parse ...</span>
    <span class="hljs-keyword">if</span> (success) {
        <span class="hljs-keyword">return</span> Config{...};
    }
    <span class="hljs-keyword">return</span> <span class="hljs-built_in">std</span>::nullopt;  <span class="hljs-comment">// Explicit failure</span>
}

<span class="hljs-comment">// Compiler warns if you don’t check.</span>
<span class="hljs-keyword">auto</span> cfg = parseConfig(”file.ini”);
<span class="hljs-keyword">if</span> (cfg) {
    <span class="hljs-comment">// Use cfg.value()</span>
}
</code></pre>
<p>The type system <strong>forces</strong> you to handle failure.</p>
<h3 id="heading-4-move-semantics-c11">4. <strong>Move Semantics (C++11)</strong></h3>
<p>This is huge for performance without sacrificing safety:</p>
<pre><code class="lang-cpp"><span class="hljs-function"><span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt; <span class="hljs-title">createLargeVector</span><span class="hljs-params">()</span> </span>{
    <span class="hljs-function"><span class="hljs-built_in">std</span>::<span class="hljs-built_in">vector</span>&lt;<span class="hljs-keyword">int</span>&gt; <span class="hljs-title">v</span><span class="hljs-params">(<span class="hljs-number">1000000</span>)</span></span>;
    <span class="hljs-comment">// ... fill vector ...</span>
    <span class="hljs-keyword">return</span> v;  <span class="hljs-comment">// Moved, not copied. (C++ 11)</span>
}

<span class="hljs-keyword">auto</span> data = createLargeVector();  <span class="hljs-comment">// No copy, just a pointer swap</span>
</code></pre>
<p>Before C++ 11, this would have copied 1 million integers, now there are no copies, the internal buffer is moved.</p>
<hr />
<h2 id="heading-but-modern-c-is-slower-right">But Modern C++ Is Slower, right?</h2>
<p>Right???</p>
<p>Well <strong>wrong</strong>! 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“.</p>
<p>So what actually happens:</p>
<pre><code class="lang-cpp"><span class="hljs-comment">// Raw pointer</span>
Widget* ptr = <span class="hljs-keyword">new</span> Widget();
<span class="hljs-keyword">delete</span> ptr;

<span class="hljs-comment">// Smart pointer</span>
<span class="hljs-built_in">std</span>::<span class="hljs-built_in">unique_ptr</span>&lt;Widget&gt; ptr = <span class="hljs-built_in">std</span>::make_unique&lt;Widget&gt;();
</code></pre>
<p>And what about the generated assembly?<br />To test this I did it with a website called <strong>Compiler Explorer</strong>, <a target="_blank" href="https://godbolt.org/z/cz47s31Eq">here</a> is the link to see it.</p>
<p><em>Used the flag [-O2 -std=c++20] for optimizations.</em></p>
<p>Modern C++ isn’t just “not slower”, it’s often <strong>faster</strong> than manual memory management:</p>
<ol>
<li><p>Move semantics avoid copies (we’ve seen this before)</p>
</li>
<li><p>Compilers optimize STL heavily</p>
</li>
<li><p>Cache locality std::vector stores elements contiguously.</p>
</li>
</ol>
<h3 id="heading-from-the-real-world">From the Real World</h3>
<p>I’ve been learning about the <a target="_blank" href="https://github.com/hyprwm/Hyprland">Hyprland</a> codebase, since I want to contribute and make some plugins for my Linux Desktop; and std::unique_ptr is nearly everywhere.<br />Hyprland might not be the best example, but is a good one for demonstrating modern C++.</p>
<p>If smart pointers really had overhead, Hyprland would not be one of the fastest compositors.</p>
<h3 id="heading-when-there-is-overhead">When There IS Overhead</h3>
<p>To be fair, there are cases with actual cost:</p>
<p><strong>std::shared_ptr has overhead:</strong></p>
<ul>
<li><p>Reference counting (atomic increments/decrements).</p>
</li>
<li><p>Extra allocation for the control block.</p>
</li>
<li><p>Use only when you actually need shared ownership.</p>
</li>
</ul>
<p><strong>std::function has overhead:</strong></p>
<ul>
<li><p>Type erasure = virtual call.</p>
</li>
<li><p>Small buffer optimization helps, but it’s there.</p>
</li>
</ul>
<p>But std::unique_ptr has <strong>zero</strong> cost. It’s an Abstraction.</p>
<h3 id="heading-the-real-cost-are-bugs">The Real Cost Are Bugs</h3>
<p>Here’s the thing: even if smart pointers had a tiny overhead (they don’t), <strong>bugs are way more expensive.</strong></p>
<p>The time spent debugging is pretty damn big for fixing it.</p>
<ul>
<li><p>Memory leak → hours tracking it down</p>
</li>
<li><p>Use-after-free → could be days, could be never</p>
</li>
<li><p>Double-free → good luck reproducing it</p>
</li>
<li><p>Time spent with smart pointers = Zero bugs from memory management, or at least close to it.</p>
</li>
</ul>
<hr />
<h2 id="heading-wrapping-up">Wrapping Up</h2>
<p>If you’re learning C++ today, learn the fundamentals first, then come back to this post and apply these concepts to your previous programs.</p>
<p><strong>Simple summary:</strong></p>
<ul>
<li><p>Smart pointers eliminate memory bugs.</p>
</li>
<li><p>RAII guarantees cleanup.</p>
</li>
<li><p>std::optional makes errors explicit.</p>
</li>
<li><p>Move semantics gives you performance.</p>
</li>
</ul>
<hr />
<p><strong>Found this helpful?</strong> Share it with someone learning C++.</p>
<p>Some stuff that is widely recommended for C++:</p>
<p><a target="_blank" href="https://www.oreilly.com/library/view/effective-modern-c/9781491908419/">Effective Modern C++</a> by Scott Meyers</p>
<p><a target="_blank" href="https://en.cppreference.com/">cppreference.com</a> This is what you will use more.</p>
<p><a target="_blank" href="https://github.com/hyprwm/Hyprland">Hyprland</a> source code for some examples I’ve used. (And hyprland is also so cool).</p>
<hr />
<p>I’ll probably be writing more about C++ on this blog while I am learning new concepts.</p>
]]></content:encoded></item></channel></rss>