Why?
What?
// myintpointer.h
class MyIntPointer {
public:
// This is the constructor
MyIntPointer(int* value);
// This is the destructor
~MyIntPointer();
int* GetValue();
private:
int* value_;
};
// myintpointer.cpp
#include "myintpointer.h"
MyIntPointer::MyIntPointer(int* value): value_{value} {}
int* MyIntPointer::GetValue() {
return value_
}
MyIntPointer::~MyIntPointer() {
// Similar to C's free function.
delete value_;
}
Don't use the new / delete keyword in your own code
We are showing for demonstration purposes
void fn() {
// Similar to C's malloc
MyIntPointer p{new int{5}};
// Copy the pointer;
MyIntPointer q{p.GetValue()};
// p and q are both now destructed.
// What happens?
}
demo551-safepointer.cpp
Type | Shared ownership | Take ownership |
---|---|---|
std::unique_ptr<T> | No | Yes |
raw pointers | No | No |
std::shared_ptr<T> | Yes | Yes |
std::weak_ptr<T> | No | No |
Also note the use of 'nullptr' in C++ instead of NULL
#include <memory>
#include <iostream>
int main() {
auto up1 = std::unique_ptr<int>{new int};
auto up2 = up1; // no copy constructor
std::unique_ptr<int> up3;
up3 = up2; // no copy assignment
up3.reset(up1.release()); // OK
auto up4 = std::move(up3); // OK
std::cout << up4.get() << "\n";
std::cout << *up4 << "\n";
std::cout << *up1 << "\n";
}
demo552-unique1.cpp
void my_func()
{
int* valuePtr = new int(15);
int x = 45;
// ...
if (x == 45)
return; // here we have a memory leak, valuePtr is not deleted
// ...
delete valuePtr;
}
int main()
{
}
#include <memory>
void my_func()
{
std::unique_ptr<int> valuePtr(new int(15));
int x = 45;
// ...
if (x == 45)
return; // no memory leak anymore!
// ...
}
int main()
{
}
std::unique_ptr<int> valuePtr(new int(47));
std::unique_ptr<int> valuePtr;
valuePtr.reset(new int(47));
//can be accessed just like when you would use a raw pointer:
std::unique_ptr<std::string> strPtr(new std::string);
strPtr->assign("Hello world");
#include <memory>
#include <iostream>
int main() {
auto up1 = std::unique_ptr<int>{new int{0}};
*up1 = 5;
std::cout << *up1 << "\n";
auto op1 = up1.get();
*op1 = 6;
std::cout << *op1 << "\n";
up1.reset();
std::cout << *op1 << "\n";
}
Can we remove "new" completely?
demo553-observer.cpp
#include <iostream>
#include <memory>
#include <utility>
int main()
{
std::unique_ptr<int> valuePtr(new int(15));
std::unique_ptr<int> valuePtrNow(std::move(valuePtr));
std::cout << "valuePtrNow = " << *valuePtrNow << '\n';
std::cout << "Has valuePtr an associated object? "
<< std::boolalpha
<< static_cast<bool>(valuePtr) << '\n';
}
#include <iostream>
#include <memory>
auto main() -> int {
// 1 - Worst - you can accidentally own the resource multiple
// times, or easily forget to own it.
auto* silly_string = new std::string{"Hi"};
auto up1 = std::unique_ptr<std::string>(silly_string);
auto up11 = std::unique_ptr<std::string>(silly_string);
// 2 - Not good - requires actual thinking about whether there's a leak.
auto up2 = std::unique_ptr<std::string>(new std::string("Hello"));
// 3 - Good - no thinking required.
auto up3 = std::make_unique<std::string>("Hello");
std::cout << *up2 << "\n";
std::cout << *up3 << "\n";
// std::cout << *(up3.get()) << "\n";
// std::cout << up3->size();
}
This method avoids the need for "new". It has other benefits that we will explore.
make_unique
is safe for creating temporaries, whereas with explicit use of new you have to remember the rule about not using unnamed temporaries.
make_unique prevents the unspecified-evaluation-order leak triggered by expressions like
demo554-unique2.cpp
foo(unique_ptr<T>(new T()), unique_ptr<U>(new U())); // unsafe*
foo(make_unique<T>(), make_unique<U>()); // exception safe // however no impact on efficiecy
Text
Text
Text
Text
auto pArr = std::unique_ptr<MyClass[]>(new MyClass[10]);
can be specialized for array std::unique_pointer<T []>
unique_ptr disposes of the controlled object by calling deleter .what what about unique_ptr to array of objects?
#include <iostream>
#include <memory>
int main()
{
const int size = 10;
std::unique_ptr<int[]> fact(new int[size]);
for (int i = 0; i < size; ++i) {
fact[i] = (i == 0) ? 1 : i * fact[i-1];
}
for (int i = 0; i < size; ++i) {
std::cout << i << "! = " << fact[i] << '\n';
}
}
shared_ptr
, unlike unique_ptr
, places a layer of indirection between the physical heap-allocated object and the notion of ownership.
shared_ptr
instances are essentially participating in ref-counted ownership of the control block.
The control block itself is the sole arbiter of what it means to “delete the controlled object.”
#include <iostream>
#include <memory>
auto main() -> int {
auto x = std::make_shared<int>(5);
std::cout << "use count: " << x.use_count() << "\n";
std::cout << "value: " << *x << "\n";
x.reset(); // Memory still exists, due to y.
std::cout << "use count: " << y.use_count() << "\n";
std::cout << "value: " << *y << "\n";
y.reset(); // Deletes the memory, since
// no one else owns the memory
std::cout << "use count: " << x.use_count() << "\n";
std::cout << "value: " << *y << "\n";
}
Can we remove "new" completely?
demo555-shared.cpp
#include <iostream>
#include <memory>
auto main() -> int {
auto x = std::make_shared<int>(1); //no ownership
//ref to objected managed by shared pointer
auto wp = std::weak_ptr<int>(x); // x owns the memory
//wp.use_count(); wp.expired();
auto y = wp.lock();
if (y != nullptr) { // x and y own the memory
// Do something with y
std::cout << "Attempt 1: " << *y << '\n';
}
}
demo556-weak.cpp
struct Person;
struct Team{
shared_ptr<Person> goalKeeper;
~Team(){cout<<"Team destructed.";}
};
struct Person{
shared_ptr<Team> team;
~Person(){cout<<"Person destructed.";}
};
int main(){
auto Barca = make_shared<Team>();
auto Valdes = make_shared<Person>();
Barca->goalKeeper = Valdes;
Valdes->team = Barca;
return 0;
}
struct Person;
struct Team{
shared_ptr<Person> goalKeeper;
~Team(){cout<<"Team destructed.";}
};
struct Person{
weak_ptr<Team> team; // This line is changed.
~Person(){cout<<"Person destructed.";}
};
int main(){
auto Barca = make_shared<Team>();
auto Valdes = make_shared<Person>();
Barca->goalKeeper = Valdes;
Valdes->team = Barca;
return 0;
}
If Barca goes out of scope, it is not deleted since the managed object is still pointed by valdee.team. When Valdes goes out of scope, its managed object is not deleted either as it is pointed by Barca.goalkeeper.
Text
Own no resourse: orrows from shared ptr
break circular dependency
We have to convert it into Shared_ptr to use it
wp.lock();
Best practice: smart pointers, and minimize raw pointers or say big NO to raw
Raw pointer should be your default parameters and return types
sometime trade-off smart vs raw
raw vs smart --->Premature Pessimizzation
if an entity must take a certain kind of ownership of the object, always use smart pointers - the one that gives you the kind of ownership you need.
If there is no notion of ownership, you may ignore use smart pointers but.
void PrintObject(shared_ptr<const Object> po) //bad
{
if(po)
po->Print();
else
log_error();
}
void PrintObject(const Object* po) //good
{
if(po)
po->Print();
else
log_error();
}
void g() {
throw std::runtime_error{""};
}
int main() {
auto ptr = new int{5};
g();
// Never executed.
delete ptr;
}
void g() {
throw std::runtime_error{""};
}
int main() {
auto ptr = std::make_unique<int>(5);
g();
}
Not safe
Safe
void g() {
throw std::runtime_error{""};
}
int main() {
auto ptr = new int{5};
auto uni = std::unique_ptr<int>(ptr);
g();
}
Not safe
two common
#include <exception>
class my_int {
public:
my_int(int const i) : i_{i} {
if (i == 2) {
throw std::exception();
}
}
private:
int i_;
};
class unsafe_class {
public:
unsafe_class(int a, int b)
: a_{new my_int{a}}
, b_{new my_int{b}}
{}
~unsafe_class() {
delete a_;
delete b_;
}
private:
my_int* a_;
my_int* b_;
};
int main() {
auto a = unsafe_class(1, 2);
}
Spot the bug
demo557-bad.cpp
#include <exception>
#include <memory>
class my_int {
public:
my_int(int const i)
: i_{i} {
if (i == 2) {
throw std::exception();
}
}
private:
int i_;
};
class safe_class {
public:
safe_class(int a, int b)
: a_(std::make_unique<my_int>(a))
, b_(std::make_unique<my_int>(b))
{}
private:
std::unique_ptr<my_int> a_;
std::unique_ptr<my_int> b_;
};
int main() {
auto a = safe_class(1, 2);
}
demo558-partial1.cpp
make_shared and make_unique
make_shared
and make_unique
wrap raw new
, just as ~shared_ptr
and ~unique_ptr
wrap raw delete
.make_shared
can be performance optimization.