引用或shared_ptr作为关联的成员变量

4

我正在编写一个名为Bar的类,Bar需要访问另一个类Foo才能发挥作用。因此,Bar使用它的实例必须比Foo的实例更长寿。

我不能确定两种写法之间的区别。以下是示例:

#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; 
}

我认为Bar1可以让用户更自由地管理Foo的生命周期,并且可能更有效率。但是当foo_所指向的Foo实例被销毁时,它会进入一个未定义的状态(不确定这里是否用词正确)。

如何处理这种情况以及为什么要采取首选方式?


为什么不在Bar构造函数中创建Foo?你需要将Foo传递给Bar吗? - 4pie0
如果你希望用户可以自由地以任何方式管理“Foo”,那么请让他们提供一个回调函数来获取有效的“Foo”实例。不过,只要不给用户的代码带来太多的复杂性,强制用户按照特定的方式管理“Foo”也不是坏事。为此,“shared_ptr”非常合适。 - StoryTeller - Unslander Monica
@piotruś 一个Foo的实例可以被多个Foo的实例使用。所以它不能成为Bar的成员。 - guini
你没有提到这一点,现在清楚了,你必须将Foo传递给Bar。 - 4pie0
2个回答

3

我认为处理这种情况的首选方式取决于具体情况。正如您所说,Bar1使用户对Foo的生命周期有更多自由,但是更加危险。它也更加高效(略微),但可能不足以引起关注。

如果你可以确定(和/或可以证明)Foo将始终超过所有Bar对象的寿命(也许你在main中在堆栈上分配了你使用的Foo),那么使用Bar1没有问题。如果不确定,则应选择Bar2。虽然使用Bar2的语义可能是错误的(也许你不希望Bar使你的Foo保持活动状态)。

这引出了第三个选择:weak_ptr。这将使用户控制Foo的生命周期,但仍允许Bar在销毁Foo时具有定义的行为。

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_;
};

2

如果您知道Foo将比Bar存在更长的时间,请使用基于引用的解决方案,因为它简单明了,并且能够表达您想要实现的目标——即Bar引用Foo。如果使用指针,则意图不太清晰。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接