std::unique_ptr
¶
The Spirit Of std::unique_ptr
¶
Unique resource ownership: only one pointer object is responsible for deleting the referenced object
No simple pointer copy allowed
⟶ would create second reference
Explicit ownership transfer needed
Only implicit when compiler can prove that no harm is done
unique_ptr
is only one (though important) user of a new language feature: Move Semantics, Rvalue References
Methods¶
Object of type std::unique_ptr
behave like pointers in any respect
(->
, *
, copy, …), except that they have methods:
Method |
Description |
---|---|
|
Initializes to |
|
Initializes to |
|
Move constructor; |
|
Returns pointer to managed object, and releases ownership |
|
Takes ownership of |
|
Pointer to currently owned object |
Basic Usage: Prevent Leaks¶
#include "sensors.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, basic)
{
std::map<std::string, std::unique_ptr<Sensor>> sensors;
RandomSensor* rs1 = new RandomSensor{20, 40};
RandomSensor* rs2 = new RandomSensor{10, 30};
ConstantSensor* cs = new ConstantSensor{36.5};
// if this threw we'd leak memory of sensor objects
auto temp = rs1->get_temperature();
ASSERT_TRUE(temp>=20 && temp<=40);
sensors["rand1"] = std::unique_ptr<Sensor>{rs1};
sensors["rand2"] = std::unique_ptr<Sensor>{rs2};
sensors["const"] = std::unique_ptr<Sensor>{cs};
}
All looks well: pointers safely wrapped in
std::unique_ptr<>
⟶ most basic intention
It’s just that little chance that something might throw between allocation and wrap
Eliminate Chance Of Leakage ⟶ Ownership¶
Straightforward try to prevent leaks:
#include "sensors.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, ownership_error)
{
std::map<std::string, std::unique_ptr<Sensor>> sensors;
std::unique_ptr<Sensor> rs1{new RandomSensor{20, 40}};
std::unique_ptr<Sensor> rs2{new RandomSensor{10, 30}};
std::unique_ptr<Sensor> cs{new ConstantSensor{36.5}};
auto temp = rs1->get_temperature();
ASSERT_TRUE(temp>=20 && temp<=40);
sensors["rand1"] = rs1;
sensors["rand2"] = rs2;
sensors["const"] = cs;
}
Apparently cannot simply assign one unique pointer onto another
Remember: one single owner
⟶ assignment would create a second
⟶ compiler helps us!
/home/jfasch/work/jfasch-home/trainings/material/soup/cxx11/030-smart-pointers/code/unique-ptr-ownership-error.cpp: In member function ‘virtual void unique_ptr_suite_ownership_error_Test::TestBody()’:
/home/jfasch/work/jfasch-home/trainings/material/soup/cxx11/030-smart-pointers/code/unique-ptr-ownership-error.cpp:17:24: error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>& std::unique_ptr<_Tp, _Dp>::operator=(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = Sensor; _Dp = std::default_delete<Sensor>]’
17 | sensors["rand1"] = rs1;
| ^~~
In file included from /usr/include/c++/11/memory:76,
from /home/jfasch/work/jfasch-home/googletest/googletest/include/gtest/gtest.h:54,
from /home/jfasch/work/jfasch-home/trainings/material/soup/cxx11/030-smart-pointers/code/unique-ptr-ownership-error.cpp:3:
/usr/include/c++/11/bits/unique_ptr.h:469:19: note: declared here
469 | unique_ptr& operator=(const unique_ptr&) = delete;
| ^~~~~~~~
⟶ Copy not possible!
Who would be responsible to destroy the object?
Ownership violation
Saving Keystrokes: std::make_unique<>()
¶
Too much typing here:
#include "sensors.h" #include <gtest/gtest.h> #include <memory> TEST(unique_ptr_suite, verbose_ctor) { std::unique_ptr<Sensor> rs1{new RandomSensor{20, 40}}; std::unique_ptr<Sensor> rs2{new RandomSensor{10, 30}}; std::unique_ptr<Sensor> cs{new ConstantSensor{36.5}}; }
std::make_unique
to the rescue:#include "sensors.h" #include <gtest/gtest.h> #include <memory> TEST(unique_ptr_suite, make_unique_auto) { auto rs1 = std::make_unique<RandomSensor>(20, 40); auto rs2 = std::make_unique<RandomSensor>(10, 30); auto cs = std::make_unique<ConstantSensor>(36.5); }
Explicitly Acknowledging Ownership Transfer: std::move()
¶
What is needed is a way of explicit ownership tranfer
⟶ Acknowledging terms and conditions
⟶ Moved-from pointer object becomes invalid
This is not specific to
unique_ptr
⟶ Move Semantics, Rvalue References
#include "sensors.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, ownership_error)
{
std::map<std::string, std::unique_ptr<Sensor>> sensors;
auto rs1 = std::make_unique<RandomSensor>(20, 40);
auto rs2 = std::make_unique<RandomSensor>(10, 30);
auto cs = std::make_unique<ConstantSensor>(36.5);
auto temp = rs1->get_temperature();
ASSERT_TRUE(temp>=20 && temp<=40);
// invalidate outside object references, and move objects to be
// owned by map
sensors["rand1"] = std::move(rs1);
sensors["rand2"] = std::move(rs2);
sensors["const"] = std::move(cs);
// outside references are gone
ASSERT_EQ(rs1.get(), nullptr);
ASSERT_EQ(rs2.get(), nullptr);
ASSERT_EQ(cs.get(), nullptr);
}
Compiler Can Prove: Implicit Ownership Transfer¶
Function returns an object by copy
⟶ compiler knows that that object will never be used anymore
To the caller this object appears as R-Value (something that cannot be assigned to)
Enter “R-Value References”
unique_ptr
has a constructor that can accept an R-Value reference⟶
&&
“Move-Constructor”
unique_ptr(unique_ptr&& u) noexcept;
#include "sensors.h"
#include <gtest/gtest.h>
#include <memory>
static std::unique_ptr<Sensor> create_random_sensor(double low, double high)
{
// do some boilerplate (like going through config) before creating
// sensor object
return std::make_unique<RandomSensor>(low, high);
}
static std::unique_ptr<Sensor> create_constant_sensor(double temp)
{
// do some boilerplate (like going through config) before creating
// sensor object
return std::make_unique<ConstantSensor>(temp);
}
TEST(unique_ptr_suite, implicit_ownership_transfer)
{
std::map<std::string, std::unique_ptr<Sensor>> sensors;
sensors["rand1"] = create_random_sensor(20, 40);
sensors["rand2"] = create_random_sensor(20, 40);
sensors["const"] = create_constant_sensor(36.5);
}
So unique_ptr
’s move constructor is responsible for handling
ownership transfer.
How To Write Code That Can Take Ownership?¶
Ok,
unique_ptr
is programmed to take ownership if possibleWhat if I have code that wants to take ownership of a
unique_ptr
(or any type that I can move from, for that matter)?⟶ Write my own move constructor
#include "sensors.h"
#include <gtest/gtest.h>
#include <memory>
class HoldsASensor
{
public:
HoldsASensor(std::unique_ptr<Sensor>&& s)
: _sensor(std::move(s)) {}
private:
std::unique_ptr<Sensor> _sensor;
};
static std::unique_ptr<Sensor> create_constant_sensor(double temp)
{
return std::make_unique<ConstantSensor>(temp);
}
TEST(unique_ptr_suite, taking_ownership_in_own_code)
{
auto has = HoldsASensor(create_constant_sensor(2));
}
Attention
Pitfall alert
HoldsASensor(std::unique_ptr<Sensor>&& s)
: _sensor(std::move(s)) {}
Note that the parameter s
is an lvalue, for that matter - it
has a name. So std::move()
is necessary. std::unique_ptr
does not support copy, by nature, so the code won’t sompile
anyway. Other types may (std::string
, for example). There be
dragons!
See Move Semantics, Rvalue References for more.
Manipulating Pointer Content¶
Method |
Description |
---|---|
|
returns contained pointer |
|
Deletes contained pointer, takes ownership of new pointer |
|
Returns contained pointer, releasing ownership |
#include "sensors.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, release)
{
auto raw = new RandomSensor{20, 30};
auto ptr = std::unique_ptr<Sensor>(raw);
auto raw1 = ptr.release();
ASSERT_EQ(raw1, raw);
ASSERT_EQ(ptr.get(), nullptr);
// me being owner now!
delete raw1;
}
TEST(unique_ptr_suite, reset)
{
std::unique_ptr<Sensor> ptr = std::make_unique<RandomSensor>(20, 30);
ptr.reset(new ConstantSensor{15}); // raw1 gone
ptr.reset(); // all gone
ASSERT_EQ(ptr.get(), nullptr);
}