This is new - I tried to push some code the our git server and the company porn filter did not allow it! After spending some time a found the problem in my code
ObjectType *type = new ObjectType();
The porn filters now a days are so sophisticated that the can find naked pointers! Wow!
No that did not happen - it’s just a start of the article
But one thing that happen was that I realize that many still uses naked pointer in C++. Why? Well maybe simply because they don’t know of std::unique_ptr<>
, std::shared_ptr<>
and std::weak_ptr<>
(not covered in this article)?
The problem with naked pointers - you and me
There is nothing wrong with the pointers them self, the main problem is you and me. We tend to forget to deallocate what we allocated. We pass pointers around without having a clear picture who is the owner of it and if it might outlive another objects that handles it. This results is crashes and/or memory leaks.
So to solve this we can use these “Smart Pointer” that will help us with resource allocation and deallocation. They also helps us handle ownership of our pointers.
The syntax of smart pointers
This is the simple part. The syntax for using a smart pointer is the same as for a raw pointer.
std::string *rawPtrString = new std::string();
std::shared_ptr<std::string> smartPtrString = std::make_shared<std::string>();
if (rawPtrString != nullptr)
std::cout << rawPtrString->size() << std::endl;
if (smartPtrString != nullptr)
std::cout << smartPtrString->size() << std::endl;
The ->
-operator is used to access the pointer member functions/variables. You can also use the smart pointer to compare against nullptr
.
To access the raw-pointer one can use .get()
, to release the pointer one can use .release()
. I will not go more into detail about the specific member functions available. More info can be found here
Maybe in some other article I will cover them more deeply.
std::unique_ptr<>
- The most simple one
The std::unique_ptr<>
is the simplest smart pointer. It hold a pointer and it’s unique. It will deallocate the pointer when the variable goes out of scope. Here is an example
Allocation/Instantiation
#include <memory>
class Foo
{
public:
Foo()
{
m_bar = std::make_unique<Bar>();
std::unique_ptr<Bar> anotherBar = std::make_unique<Bar>();
}
private:
std::unique_ptr<Bar> m_bar;
};
This example has two std::unique_ptr<>
:s, m_bar
as a member variable in Foo
and anotherBar
as a variable in the constructor of Foo
. Both m_bar
and anotherBar
is created in the constructor.
m_bar = std::make_unique<Bar>();
std::unique_ptr<Bar> anotherBar = std::make_unique<Bar>();
Both are using std::make_unique<>
to allocate the new Bar-objects on the heap memory. The difference between m_bar
and anotherBar
is simply that m_bar
will deallocate Bar
when the Foo
object is deallocated and anotherBar
is deallocated when exiting the constructor (it’s scope).
So what std::unique_ptr
does is simple deallocating it’s resource when exiting it’s scope!
std::make_unique<>
is a C++14 feature. If using C++11 you would have to use m_bar = std::unique_ptr<Bar>(new Bar())
.
Why you can’t copy a std::unique_ptr
By design a std::unique_ptr
isn’t copyable. Nothing strange with that when you think about it. If you would copy something that is unique, it’s not unique any more.
But instead of copy we can transfer ownership by using the move-semantics.
class Foo
{
public:
Foo(std::unique_ptr<Bar> bar) {
m_bar = std::move(bar);
}
private:
std::unique_ptr<Bar> m_bar;
};
int main(int argc, char *argv[])
{
auto bar = std::make_unique<Bar>();
Foo f(std::move(bar));
}
As you can see in the example above std::move(bar)
is use to move the ownership from one to another.
std::shared_ptr<>
- pointer with build in ref. counter
The difference between a std::unique_ptr
and std::shared_ptr
is that the std::shared_ptr
has a shared ownership instead of the unique ownership that std::unique_ptr
has.
So what does shared ownership mean? Simply put it the last owner is the one making sure the the resource is deallocated. In Arthor O’Dweyers talk about smart-pointers at CppCon he had an analogy about the last person exiting a room is responsible for turning the lights off.
The rules for this shared responsibility (turning the light of) is simple. When you enter the room you put a token into a jair, when you exit you take one token out. If the jair is empty when you take you token out - you turn the lights of.
std::shared_ptr
works in the same way. For every shared-owner that exists the reference counter is increased and when the pointer loose a shared-owner it decrease the reference counter. If the counter is zero it will deallocate the pointer.
This creates a way of having a shared ownership of pointers. You can create a std::shared_ptr
and pass it to another object that also holds a reference to it and no longer worry about when the resource is deallocated.
class Foo
{
public:
Foo(std::shared_ptr<Bar> ptr) {
m_bar = ptr;
}
void work() {
m_bar->work();
}
private:
std::shared_ptr<Bar> m_bar;
};
int main(int argc, char *argv[])
{
auto bar = std::make_shared<Bar>();
auto foo = std::make_unique<Foo>(bar);
foo->work();
// Reset bar
bar.reset();
// Still okay, because m_bar still has a
// reference to bar created in the beginning
foo->work();
}
Why use std::make_unique
and std::make_shared
?
A *a = std::unique_ptr<A>(new A());
auto a = std::make_unique<A>();
Why is the later better. Well two things. First of all std::make_unique<>()
is a bit more optimized, but let’s not get into that. Second and the more important reason to use std::make_unique<>()
and std::make_shared<>()
- You can end up with code that doesn’t contain any new
or delete
.
When I first learned C++ the thing you should do was to make sure to add your delete
that “matched” the new
you just added. But now we don’t need delete
anymore so let’s get rid of new
also and get rid of that said rule!
Custom deleters
Both std::unique_ptr
and std::shared_ptr
handles defining a custom deleter. This can be used for customizing which code needs to be executed for releasing the resources. This is especially be useful when working with many C-libraries.
For example when working with libLXC you use lxc_container_new(...)
and lxc_container_put(...)
to allocate and deallocate a container. Using smart-pointers this can be done by:
struct LXCDeleter
{
void operator()(lxc_container *ptr) const {
lxc_container_put(ptr);
}
};
auto filePtr = std::unique_ptr<lxc_container, LXCDeleter>{ lxc_container_new("foo", nullptr) };
The above example will create a std::unique_ptr<>
by using lxc_container_new("foo", nullptr)
to allocated the container. When the unique_ptr goes out of scope it will use the custom deleter LXCDeleter
which will call lxc_container_ptr(ptr)
to deallocate!
Another example is when working with files.
struct FileDeleter
{
void operator()(FILE *ptr) const {
fclose(ptr);
}
};
auto filePtr = std::unique_ptr<FILE, FileDeleter>{ fopen("foo.txt", "rw") };
The above code will make sure to close the file when the std::unique_ptr
goes out of scope.
Whats next?
Well you tell me. I got some ideas to continue write about std::week_ptr<>
or maybe go into more detail one how to reference-counting works, or how to design good API:s with smart-pointers. There is also the fun std::enable_shared_from_this
that can be something to write about.
But most important - start writing code with no naked new
or delete
.
Also spam your company porn filter with this.