4.8 KiB
4.8 KiB
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<>
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