new
/delete
or new[]
/delete[]
Pretty much this single rule: don’t use raw pointers for new
‘d objects in modern c++!
Why? Because you’re liable for delete
‘ing the pointer. If it’s not, you get a memory leak. If you run your program for a long time, you’ll be wondering if you really bought an 16 GB macbook.
This is a very subtle way to screw up:
void bar(){
Foo* f = new Foo();
... // exception occurs!
delete f; // We're good... right?
};
try{
bar();
} catch(std::string& e){
std::cout << e.what() << std::endl;
}
As a result of the exception, our flow is intercepted and we don’t get to delete f;
. That’s a memory leak right there.
C++ objects are nice. They have destructors. Use it:
void bar(){
std::shared_ptr<Foo> f(new Foo());
... // exception occurs!
}; // we left scope, f is destroyed!
...
The standard library has std::auto_ptr
, std::shared_ptr
, and std::unique_ptr
for the most basic RAII pointer management. You probably shouldn’t use std::auto_ptr
unless you need to though. We’ll explain why below:
There’s multiple definitions of the idea “copy”:
std::auto_ptr<Foo> f1(new Foo());
std::auto_ptr<Foo> f2(new Foo());
f1 = f2; // f1 is nullptr
// denote content as c1. Count(c1) = 1
std::shared_ptr<Foo> f1(new Foo());
// denote content as c2. Count(c2) = 1. This syntax works too.
std::shared_ptr<Foo> f2 = std::make_shared<Foo>();
// Count(c1) = 0, Count(c2) = 2
f1 = f2;
void foo(std::unique_ptr<Foo> f){
...
}
std::unique_ptr<Foo> f1(new Foo());
// COMPILER ERROR! Can't just copy unique_ptr!
std::unique_ptr<Foo> f2 = f1;
// COMPILER ERROR AGAIN!
foo(f1);
// We're OK! Explicit move!
std::unique_ptr<Foo> f2 = std::move(f1);
// We're OK! Explicit move!
foo(std::move(f2));
std::weak_ptr<Foo> f1; // empty right now
{
std::shared_ptr<Foo> f2 = std::make_shared<Foo>();
f1 = f2;
f1.use_count(); // returns 1.
std::shared_ptr<Foo> f3 = f1.lock(); // takes ownership of the shared_ptr.
... // do stuff with f3
} // unlocks, f2 and f3 are destroyed.
f1.use_count(); // returns 0
f1.lock(); // returns an empty shared_ptr!
Sometimes, an old library will not be using your fancy std::???_ptr
’s, and will require passing in pointers themselves. You can’t just pass in a smart pointer and expect it to work:
void bar(Foo* f){
...
}
std::shared_ptr<Foo> f = std::make_shared<Foo>();
bar(f); // Error! Not the correct type :(
In all std::???_ptr
’s, you can call the function get()
to retrieve the underlying pointer:
void bar(Foo* f){
...
}
std::shared_ptr<Foo> f = std::make_shared<Foo>();
bar(f.get()); // We're good!
However, the drawback is that .get()
is often pretty ugly if you wanted a smooth interface.
You can specify a copy constructor, and you wouldn’t need the .get()
:
class FooPointer{
public:
FooPointer(Foo f){
_f = f;
}
public Foo(){
return get(); // returns pointer
}
public get(){
return _f;
}
...
}
void bar(Foo f){
...
}
FooPointer f;
bar(f); // Works! Implicitly calls Foo()!
However, the drawback is that you might cast FooPointer
to Foo
on accident. That’s why I don’t think anyone should use this unless it’s of great convenience.
new
/delete
or new[]
/delete[]
Pretty self explanatory:
std::string* s = new std::string("sup");
std::string* s_ = new std::string[100];
delete s;
delete [] s_;
Here’s a picture for reference. You can see what happens when you make a mistake:
Typedefs are already super confusing if you don’t use the simple ones, but to add fuel to the fire:
typedef std::string StringTenTuple[10];
std::string* s = new StringTenTuple;
delete s; // NOPE!
delete [] s; // YUP!
That triggers me holy.
In fact, in cppreference
, they even stated that there are some issues with this, and that’s why std::make_shared<Foo>(...)
was created:
void bar(std::shared_ptr<Foo> f, int v){
...
}
bar(std::shared_ptr<Foo>(new Foo()), bad_function()); // DONT DO THIS!
Why? Because of the way execution is done on these variables! There are 4 statements here:
new Foo()
bad_function()
std::shared_ptr<Foo>(...)
bar(...)
Now, if we followed this order, we would be in big trouble! We allocated Foo
on the heap, but before we were able to safely stuff it into std::shared_ptr<Foo>(...)
, we ran into an exception in bad_function
.
Did someone say memory leak?
That’s why the standard library made std::make_shared<Foo>(...)
:
void bar(std::shared_ptr<Foo> f, int v){
...
}
bar(std::make_shared<Foo>(), bad_function()); // THIS IS OK!
Since steps 1 and 3 are now in the same step.
However, just don’t do this in the first place. Create your smart pointer on its own line.
void bar(std::shared_ptr<Foo> f, int v){
...
}
std::shared_ptr<Foo> f(new Foo()); // On its own line
bar(f, bad_function()); // ALWAYS SAFE!