Skip to content

Latest commit

 

History

History
217 lines (155 loc) · 5.14 KB

smart_pointers_unique_ptr.md

File metadata and controls

217 lines (155 loc) · 5.14 KB

std::unique_ptr<>


std::unique_ptr<>

Traits

  • one object == one owner
  • destructor destroys the object
  • copying not allowed
  • moving allowed
  • can use custom deleter

unique pointers


std::unique_ptr<> usage

  • Old style approach vs modern approach
#include <iostream> // old-style approach


struct Msg {
    int getValue() { return 42; }
};

Msg* createMsg() {
    return new Msg{};
}

int main() {
    auto msg = createMsg();

    std::cout << msg->getValue();
    delete msg;
}
#include <memory> // modern approach
#include <iostream>

struct Msg {
    int getValue() { return 42; }
};

std::unique_ptr<Msg> createMsg() {
    return std::make_unique<Msg>();
}

int main() {
    // unique ownership
    auto msg = createMsg();

    std::cout << msg->getValue();
}

std::unique_ptr<> usage

  • Copying is not allowed
  • Moving is allowed
std::unique_ptr<MyData> source(void);
void sink(std::unique_ptr<MyData> ptr);

void simpleUsage() {
    source();
    sink(source());
    auto ptr = source();
    // sink(ptr);       // compilation error
    sink(std::move(ptr));
    auto p1 = source();
    // auto p2 = p1;    // compilation error
    auto p2 = std::move(p1);
    // p1 = p2;         // compilation error
    p1 = std::move(p2);
}
std::unique_ptr<MyData> source(void);
void sink(std::unique_ptr<MyData> ptr);

void collections() {
    std::vector<std::unique_ptr<MyData>> v;
    v.push_back(source());

    auto tmp = source();
    // v.push_back(tmp); // compilation error
    v.push_back(std::move(tmp));

    // sink(v[0]);       // compilation error
    sink(std::move(v[0]));

}

std::unique_ptr<> cooperation with raw pointers

#include <memory>

void legacyInterface(int*) {}
void deleteResource(int* p) { delete p; }
void referenceInterface(int&) {}

int main() {
    auto ptr = std::make_unique<int>(5);
    legacyInterface(ptr.get());
    deleteResource(ptr.release());
    ptr.reset(new int{10});
    referenceInterface(*ptr);
    ptr.reset(); // ptr is a nullptr
    return 0;
}
  • get() – returns a raw pointer without releasing the ownership
  • release() – returns a raw pointer and release the ownership
  • reset() – replaces the manager object
  • operator*() – dereferences pointer to the managed object

std::make_unique()

#include <memory>

struct Msg {
    Msg(int i) : value(i) {}
    int value;
};

int main() {
    auto ptr1 = std::unique_ptr<Msg>(new Msg{5});
    auto ptr2 = std::make_unique<Msg>(5);   // equivalent to above
    return 0;
}

std::make_unique() is a factory function that produce unique_ptrs

  • added in C++14 for symmetrical operations on unique and shared pointers
  • avoids bare new expression

std::unique_ptr<T[]>

struct MyData {};

void processPointer(MyData* md) {}
void processElement(MyData md) {}

using Array = std::unique_ptr<MyData[]>;

void use(void)
{
    Array tab{new MyData[42]};
    processPointer(tab.get());
    processElement(tab[13]);
}
  • During destruction
    • std::unique_ptr<T> calls delete
    • std::unique_ptr<T[]> calls delete[]
  • std::unique_ptr<T[]> has additional operator[] for accessing array element
  • Usually std::vector<T> is a better choice

Exercise: ResourceD

  1. Compile and run ResourceD application
  2. Check memory leaks under valgrind
  3. Fix memory leaks with a proper usage of delete operator
  4. Refactor the solution to use std::unique_ptr<>
  5. Use std::make_unique()