std::unique_ptr
¶
The Spirit Of std::unique_ptr
¶
Unique resource ownership: only one pointer object is responsible for deallocation of the referenced object
No simple pointer copy allowed
⟶ would create second reference
Explicit ownership transfer needed ⟶ Move-only
Implicit ownership transfer only 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
Creating std::unique_ptr
Instances¶
Raw pointer at the basis
std::unique_ptr<>
wrapped around⟶ controlled/deterministic deallocation
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, basic_creation)
{
BigData* data = new BigData(100, 'a');
std::unique_ptr<BigData> pdata(data);
}
More condensed usage:
std::make_unique<T>()
Convenience function that
allocates
T
, usingnew
forwards its arguments to
T
’s constructor
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, more_condensed_creation)
{
auto pdata = std::make_unique<BigData>(100, 'a');
}
Inhibited: Copying std::unique_ptr
Instances¶
Uniqueness: no two pointers can point to the same object
⟶ Copy constructor (and assignment operator) are deleted
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, copy_inhibited)
{
auto pdata = std::make_unique<BigData>(100, 'a');
auto copy = pdata; // <-- copy initializer inhibited
}
Compiler error: copy constructor is deleted
(Same with copy assignment)
error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = BigData; _Dp = std::default_delete<BigData>]’
8 | auto copy = pdata; // <-- copy initializer inhibited
| ^~~~~
Inhibited: Pass By Copy¶
Consequence: pass-by-copy is also inhibited
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
static void foo(std::unique_ptr<BigData> d) // <-- pass by copy
{
// ... do something with p
}
TEST(unique_ptr_suite, pass_by_copy_inhibited)
{
auto pdata = std::make_unique<BigData>(100, 'a');
foo(pdata); // <-- copy initializer inhibited
}
error: use of deleted function ‘std::unique_ptr<_Tp, _Dp>::unique_ptr(const std::unique_ptr<_Tp, _Dp>&) [with _Tp = BigData; _Dp = std::default_delete<BigData>]’
13 | foo(pdata); // <-- copy initializer inhibited
| ~~~^~~~~~~
Passing Temporary Objects Are Moved Automatically¶
Compiler knows that a temprary object has no name
⟶ Cannot be known to anyone but the compiler
⟶ Compiler can do with it what he wants
⟶ Move it into the function’s parameter (see here)
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
static void foo(std::unique_ptr<BigData> d)
{
// ... do something with p
}
TEST(unique_ptr_suite, temporary_object_moved)
{
foo(std::make_unique<BigData>(100, 'a')); // <-- std::unique_ptr move ctor called
}
std::move()
: Moving Objects Explicitly¶
Developer might decide that they don’t need a variable anymore
⟶ Explicit move using
std::move()
Moved-from object is in invalid state afterwards
In case of
std::unique_ptr
:nullptr
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
static void foo(std::unique_ptr<BigData> d) // <-- pass by copy
{
// ... do something with p
}
TEST(unique_ptr_suite, pass_by_copy_explicit_move)
{
auto pdata = std::make_unique<BigData>(100, 'a');
foo(std::move(pdata)); // <-- explicit ownership transfer
ASSERT_EQ(pdata, nullptr); // <-- pdata **INVALID** after move
}
Object Access: std::unique_ptr
Behaves Like A Pointer¶
Has overloaded operators that make it behave like a pointer
operator->()
operator*()
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, is_a_pointer)
{
auto pdata = std::make_unique<BigData>(100, 'a');
ASSERT_EQ(pdata->at(42), 'a');
ASSERT_EQ((*pdata).at(42), 'a');
}
Raw Pointer Access: .get()
¶
Not the plan
Sometimes needed though
⟶ Careful!
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, raw_pointer_get)
{
auto pdata = std::make_unique<BigData>(100, 'a');
BigData* raw = pdata.get();
ASSERT_EQ(raw->at(42), 'a'); // <-- operator->()
ASSERT_EQ((*raw).at(42), 'a'); // <-- operator*()
ASSERT_EQ(raw, pdata.get()); // <-- .get() leaves pdata untouched
}
Re-Gaining Control: .release()
¶
Return pointed-to object to caller
Modifies
std::unique_ptr
object to not point to anythingCaller now responsible for object deallocation
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, raw_pointer_release)
{
auto pdata = std::make_unique<BigData>(100, 'a');
BigData* raw = pdata.release(); // <-- developer is now responsible for `raw`
ASSERT_EQ(pdata, nullptr);
ASSERT_EQ(raw->at(42), 'a');
delete raw; // <-- (here)
}
Deallocation Before Pointer Destruction: .reset()
¶
Nulling out pointer object
With all consequences: pointed-to object deallocated
Pointer object still alive but invalid
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, reset)
{
auto pdata = std::make_unique<BigData>(100, 'a');
pdata.reset(); // <-- deallocate managed object
ASSERT_EQ(pdata, nullptr);
}
Variant: exchanging pointer-to object with different (or even same) object
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, reset_exchange)
{
auto pdata = std::make_unique<BigData>(100, 'a');
BigData* newdata = new BigData(1000, 'b');
pdata.reset(newdata); // <-- deallocate, and point to new object
ASSERT_EQ(pdata.get(), newdata);
}
Custom Deleter¶
Additional template parameter
Deleter
Callable
Signature
void(T*)
Additional constructor parameter of that type
#include "big-data.h"
#include <gtest/gtest.h>
#include <memory>
TEST(unique_ptr_suite, deleter)
{
bool deleted = false;
auto deleter = [&deleted](BigData* obj) {
deleted = true;
delete obj;
};
std::unique_ptr<BigData, decltype(deleter)> pdata(new BigData(100, 'a'), deleter);
pdata.reset();
ASSERT_TRUE(deleted);
}