trainings/AdvancedCppV2/Presentation/smart_pointers_weak_ptr.md

4.8 KiB

Cyclic dependencies

cyclic dependencies
  • Cyclic dependency is where you have class A with self-referencing member.
  • Cyclic dependency is where you have two classes A and B where A has a reference to B which has a reference to A.
  • How to fix it?

std::weak_ptr<> to the rescue

  • does not own an object
  • observes only
  • must be converted to std::shared_ptr<> to access the object
  • can be created only from a std::shared_ptr<>
weak pointers

std::weak_ptr<> usage

#include <memory>
#include <iostream>

struct Msg { int value; };

void checkMe(const std::weak_ptr<Msg> & wp) {
    std::shared_ptr<Msg> p = wp.lock();
    if (p)
        std::cout << p->value << '\n';
    else
        std::cout << "Expired\n";
}

int main() {
    auto sp = std::shared_ptr<Msg>{new Msg{10}};
    auto wp = std::weak_ptr<Msg>{sp};
    checkMe(wp);
    sp.reset();
    checkMe(wp);
}
> ./a.out
10
Expired

std::shared_ptr<> cyclic dependencies

  • How to solve this problem?
#include <memory>

struct Node {
    std::shared_ptr<Node> child;
    std::shared_ptr<Node> parent;
};

int main () {
    auto root = std::shared_ptr<Node>(new Node);
    auto child = std::shared_ptr<Node>(new Node);
    root->child = child;
    child->parent = root;
}

Breaking cycle - solution

  • Use std::weak_ptr<Node> in one direction
#include <memory>
struct Node {
    std::shared_ptr<Node> child;
    std::weak_ptr<Node> parent;
};

int main () {
    auto root = std::shared_ptr<Node>(new Node);
    auto child = std::shared_ptr<Node>(new Node);
    root->child = child;
    child->parent = root;
}

==148== All heap blocks were freed -- no leaks are possible


std::weak_ptr<> real life usage (1)

struct Image {
    /* data */
};

class Screen;

class Downloader {
public:
    void downloadImage(const std::string& url, std::function<void(Image)> callback, std::weak_ptr<Screen> weak_ptr) {
        std::cout << "Start downloading!" << std::endl;
        std::thread(&Downloader::download, this, url, callback, weak_ptr).detach();
    }

private:
    void download(const std::string& url, std::function<void(Image)> callback, std::weak_ptr<Screen> weak_ptr) {
        // Simulate download process
        std::cout << "Download in progress!" << std::endl;
        std::this_thread::sleep_for(std::chrono::seconds(3));
        std::cout << "Download Finished!" << std::endl;
        if (auto lock = weak_ptr.lock()) {
            callback(Image{});
        } else {
            std::cout << "Screen object expired!\n";
        }
    }
};

class Screen : public std::enable_shared_from_this<Screen> {
public:
    Screen(Downloader* downloader)
        : downloader_(downloader) {}

    void printImage(const std::string& url) {
        std::cout << "Waiting for image download!" << std::endl;
        downloader_->downloadImage(
            url, [&](Image image) { onImageDownloaded(std::move(image)); }, weak_from_this());
    }

private:
    void onImageDownloaded(Image&& image) {
        // Do some action on image
        std::cout << "Got Image!\n";
    }

    Downloader* downloader_;
};

int main() {
    auto downloader = std::make_unique<Downloader>();
    auto screen = std::make_shared<Screen>(downloader.get());

    screen->printImage("www.image.com/sth/special.img");
    std::this_thread::sleep_for(std::chrono::seconds(2));
    screen = nullptr;
    std::this_thread::sleep_for(std::chrono::seconds(2));
}

std::weak_ptr<> real life usage (2)

Output:

Waiting for image download!
Start downloading!
Download in progress!
Download Finished!
Screen object expired!

When we delete line screen = nullptr;

Waiting for image download!
Start downloading!
Download in progress!
Download Finished!
Got Image!

std::weak_ptr<> real life usage (3)

Use std::weak_ptr when:

  • Need to observe a lifetime of the object, without having an ownership
  • Want to break cyclic dependencies