Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Reference or shared_ptr as member for association

Tags:

c++

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?

like image 513
guini Avatar asked Oct 24 '25 18:10

guini


2 Answers

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_;
};
like image 97
Daniel Gallagher Avatar answered Oct 26 '25 08:10

Daniel Gallagher


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.

like image 31
Wojtek Surowka Avatar answered Oct 26 '25 08:10

Wojtek Surowka