I'm writing a class Bar. Bar needs to access an other class Foo to be useful. So the instance of Foo has to outlive the instance of Bar using it.
I can't decide between two ways to write this. Here is an example:
#include <iostream>
#include <memory>
using namespace std;
struct Foo {
Foo(int _x) : x_(_x) {}
~Foo() {}
int x_;
};
struct Bar1 {
Bar1(Foo& _foo) : foo_(_foo) {}
void print_foo() {cout << foo_.x_ << endl;}
private:
Foo& foo_;
};
struct Bar2 {
Bar2(shared_ptr<Foo> _foo) : foo_{move(_foo)} {}
void print_foo() {cout << foo_->x_ << std::endl;}
private:
shared_ptr<Foo> foo_;
};
int main()
{
Foo f1{1};
shared_ptr<Foo> f2 = make_shared<Foo>(2);
Bar1 b1(f1);
b1.print_foo();
Bar2 b2(f2);
b2.print_foo();
return 0;
}
I think, that Bar1 gives the user more freedom on how to manage the lifetime of Foo and it probably is more efficient. But it goes in an undefined (not sure if this is the right word here) state, when the instance of Foo to which foo_ refers is destroyed.
What is the preferred way to handle a situation like this and why?
I think the preferred way to handle this situation depends on the specifics of the situation. As you identified, Bar1 gives the user more freedom with the lifetime of Foo, and is more dangerous. It is also more efficient (slightly), but probably not enough to be a concern.
If you know for a fact (and/or can prove) that Foo will always outlive all of your Bar objects (maybe you allocate the Foo you use on the stack in main), there is no problem using Bar1. If you are unsure, Bar2 would be the way to go. Though the semantics may be wrong with Bar2 (maybe you don't want Bar to keep your Foo alive).
This leads us to a third option: weak_ptr. This would give the user control of the lifetime of Foo, but still allow Bar to have defined behavior when the Foo is destroyed.
struct Bar3 {
Bar3(std::weak_ptr<Foo> _foo) : foo_(_foo) {}
void print_foo_v1() {
// throws if foo_'s object has been destroyed
std::shared_ptr<Foo> foo(foo_);
std::cout << foo->x_ << std::endl;
}
void print_foo_v2() {
// returns nullptr if foo_'s object has been destroyed
std::shared_ptr<Foo> foo(foo_.lock());
if (foo) {
std::cout << foo->x_ << std::endl;
}
}
private:
std::weak_ptr<Foo> foo_;
};
If you know that Foo will outlive Bar use reference-based solution because of simplicity and because it expresses what you want to achieve - that Bar references Foo. If you use pointers then the intent is not that clear.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With