

Discover more from Bitesized Engineering
RAII and Smart Pointers - smarter way to work with your memory
Chronicles of Memory Management - Part 15
Hi! 👋
We’re nearing the end of these C++ Memory Allocation series and there are two more things that I want to talk about — RAII and Smart pointers, which is the topic of today, and Alternative Memory Allocation libraries which will be the topic of the next article.
As usual, first we go with the bitesized infographic and then the more detailed info will follow. Enjoy!
Cool! I hope you found that useful?
I’d like to share some additional thoughts on RAII (Resource Allocation Is Initialization) first. I was actually reading the “Tour of C++" by the C++’s father himself - Bjarne Stroustrup and I clearly remember him saying “so people kept forgetting to deallocate memory, opening up various memory leaks, so I came up with this acronym - RAII, which was supposed to put an end to it”.
Honestly speaking, I tried really hard to understand the logic behind the name and, sadly, I just kept failing; miserably so. It just doesn’t make ANY sense to me. I was happy to learn that others find it “weird”, so to speak, as well, and people apparently came up with a better acronym - Stack Bound Resource Management (SBRM). Now this one is troubling to me as well, but at least it communicates the idea a bit better. So let’s focus on that.
What both RAII and SBRM advocate is that any memory allocation (e.g. constructing any object or allocating raw bits of memory) should be wrapped inside a SINGLE object. And this object should have Destructor defined which should cleanup any initializations that were made. Here’s a simple example:
class DataPackage
{
private:
int* data;
public:
DataPackage()
{
data = new int(12345);
}
~DataPackage()
{
delete data;
}
};
void useData()
{
DataPackage* data = new DataPackage();
// Do something useful with data ..
// And feel free to forget to remove it ...
// Once the function ends and "data" goes
// out of scope, destructor will be called
// and dynamic memory will be cleaned up.
}
What we do above is we do dynamic allocation of an INT, but we do it inside an object. And we can really forget about it because once the pointer to DataPackage goes out of scope, the destructor will be called and the memory will be cleaned up.
Compare that to this:
void leakyData()
{
int* data = new int(12345);
// Do something useful with data ..
// Unless you delete it explicitly, you leak 32 bits
// every time you call leakyData() ;/
}
That makes sense? I hope it does. And I guess that “Stack Bound Resource Management” makes more sense as well now. All your locally allocated variables are put on Stack (hence the name) and once they go out of scope, Destructor is automatically called (assuming there IS one, obviously). So it really boils down to (ab)using this nifty feature of Stack vars being cleaned up automatically. It’s like a free garbage collector!
I was also happy to learn that Rust actually enforces RAII to be used, which is one of the principles that language is built on :)
Speaking of stuff that relies on RAII, not mentioning Smart Pointers would be a disservice to reader :) I was actually thinking of naming the article “There’s nothing inherently Smart about Smart Pointers”, but eventually decided against it. Smart Pointers are smart because they rely on simple things — Stacks that call Destructors once the object goes out of scope :)
If you want to learn details, I thinks this article that mentions them back in 2002 can give you a solid context, whereas this StackOverflow answer goes in a really nice and yet approachable depth.
Given that I understand you are likely in a hurry, I’ll try to be short and sweet. Smart Pointers rely on Destructors and that’s fine. What’s different about them is how are they supposed to be used. Unique Pointer is just like your regular pointer - it encapsulates a single resource. Shared Pointer is more of a thing that you might want to copy to multiple places, while letting the Shared Pointer keep the count. Once the count drops to zero - destructor removes the original resource. And finally, Weak Pointer is similar to Shared Pointer, except that it doesn’t count :) And that’s actually all there is to it.
Here are some really simple examples to give you a clue:
auto data = std::make_unique<int>(12345);
auto shared_data = std::make_shared<int>(12345);
// This won't work - unique_ptr can NOT be copied and C++ reports an error.
passDataByValue(data);
// This will work, and shared_data's ref. count will increase by 1 here
passDataByValue(shared_data);
Here’s a live example:
Notice the “_Uses” being set to 1 here. Now see what happens once we pass it by value:
As you can see, _Uses is now 2. What this means is that once we go out of scope of passDataByValue(), destructor of Shared Pointer will be called, but given that _Uses is > 1 - the original object won’t be removed. It is only after the SECOND function is done that the object will be cleaned up.
And that’s about it for today! :) If you found it useful, I’d appreciate if you share it with your network:
Next time I’m going to talk about alternative libraries for Memory Allocation. Until then!
Other articles from the C++ Memory Management series: