【问题标题】:Shared resource ownage using std::weak_ptr使用 std::weak_ptr 共享资源所有权
【发布时间】:2013-08-29 16:41:36
【问题描述】:

我想知道如何(使用 C++11 并希望使用向后(boost 或 TR1)兼容的智能指针类型)实现:

一个类实例 (ModelController) 拥有一个资源 (InputConsumer),而另一个组件 (InputSender,在这种情况下是一个单例) 可以访问它。

模型是InputSender 持有对InputConsumers 的引用列表,其中会有很多。

ModelController 可能没有、一个或多个InputConsumers,并且可能有多个ModelControllers。 InputSender 不知道。

最好的方法是:InputSender 跟踪分配给它的 InputConsumers 的一种方式,它可以自行确定个人 InputConsumers 是否有效。

在我看来weak_ptr 非常适合此目的,因为它们的使用需要检查此条件。

如果 InputSender 停止跟踪其任何 weak_ptr 引用,则不会发生任何不好的事情,相应的 InputConsumers 将经历无线电静默。

如果ModelController 被删除,或者如果ModelController 删除了它的一些InputConsumers,任何已注册的InputSenders 将在他们下次尝试访问它们时识别出他们没有不再存在,并且可以清理,无需发送消息或执行任何操作。

所以问题是,这是使用shared_ptrweak_ptr 的合适情况吗?我想知道shared_ptr 是否完全合适,因为InputConsumers 在概念上拥有 由他们的ModelControllers,所以它们应该是成员变量。我不知道ModelController 只通过shared_ptr 管理它们有多大意义。我不知道unique_ptr 是否与weak_ptr 一起使用。我应该只管理ModelController 的ctor/dtor 中的shared_ptrs 吗?

可能还有一个众所周知的(对我来说不是!)设计模式,所以如果有人知道这样的事情,请告诉我。

【问题讨论】:

    标签: c++ c++11 shared-ptr smart-pointers weak-ptr


    【解决方案1】:

    我在共享指针方面没有很多专业知识,但是是的,这似乎是对 weak_ptr 的非常恰当的用法。

    在这种情况下,你只是很生气:

    1. 您希望将InputConsumers 直接用作ModelControllers 的成员,因为它是一种微不足道的所有权关系。
    2. 您必须使用shared_ptr 才能使其与weak_ptr 一起使用。

    我认为这可以通过使用shared_ptr 作为成员对象的别名 来解决。根据C++.com

    此外,shared_ptr 对象可以通过指针共享所有权 同时指向另一个对象。这种能力是 称为别名(参见构造函数),通常用于指向 成员对象同时拥有它们所属的对象。

    我自己从来没有做过,但这似乎适合你的情况:

    • 拥有InputConsumers 成员ModelControllers
    • 每个人都有一个别名shared_ptr
    • 使用weak_ptrs 在InputSender 中引用它们

    编辑

    这是一个完整的最小工作示例:

    #include <iostream>
    #include <memory>
    
    using namespace std;
    
    // A class to try our smart pointers on
    struct Foo 
    {
        Foo() { cout << "constructing Foo\n"; }
        ~Foo() { cout << "destructing Foo\n"; }
    };
    
    // A class that owns some Foo as members
    struct Owner
    {
        // The actual members
        Foo foo1;
        Foo foo2;
    
        // A fake shared pointer whose purpose is:
        //   1) to be of type shared_ptr<>
        //   2) to have the same lifetime as foo1 and foo2
        shared_ptr<Owner> self;
    
        // A fake deleter that actually deletes nothing 
        struct Deleter
        {
            void operator() (Owner *) { cout << "pretend to delete Owner\n"; }
        };
    
        Owner() : self(this, Deleter()) { cout << "constructing Owner\n"; }
        ~Owner()                        { cout << "destructing Owner\n"; }
    };
    
    // A class that holds a reference to a Foo
    struct Observer
    {
        // A reference to a Foo, as a weak pointer
        weak_ptr<Foo> foo_ptr;
    
        Observer(const shared_ptr<Foo> & foo_ptr) : foo_ptr(foo_ptr)
        {
            cout << "constructing Observer\n";
        }
        ~Observer() { cout << "destructing Observer\n"; }
    
        void check()
        {
            if(foo_ptr.expired())
                cout << "foo expired\n";
            else
                cout << "foo still exists\n";
        }   
    };  
    
    int main()
    {
        // Create Owner, and hence foo1 and foo2
        Owner * owner = new Owner;
    
        // Create an observer, passing an alias of &(owner->foo1) to ctor
        Observer observer(shared_ptr<Foo>(owner->self, &(owner->foo1)));
    
        // Try to access owner->foo1 from observer
        observer.check();
        delete owner;
        observer.check();
    
        return 0;
    }
    

    打印出来:

    constructing Foo
    constructing Foo
    constructing Owner
    constructing Observer
    foo still exists
    destructing Owner
    pretend to delete Owner
    destructing Foo
    destructing Foo
    foo expired
    destructing Observer
    

    棘手的部分是能够创建一个weak_ptrowner-&gt;foo1(对于foo2 将是相同的)。为此,我们首先需要一个shared_ptr,它是owner-&gt;foo1 的别名。这只能通过以下方式完成:

    shared_ptr<Foo> alias(other_shared_ptr, &(owner->foo1));
    

    其中other_shared_ptr 是一个shared_ptr&lt;T&gt;,其生命周期至少与owner-&gt;foo1 的生命周期一样长。为了实现这一点,使用shared_ptr&lt;T&gt; 也是一个好主意,它也是owner 的成员,因为它可以确保生命周期相同。最后,我们需要一个有效的非空指针给它,因为我们不想在堆上创建任何东西,我们必须使用一个现有的对象。 this 是一个很好的候选者,因为我们知道它是有效的,并且只有在其成员被销毁后才会被销毁。因此我们的other_shared_ptr 是例如:

    shared_ptr<Owner> self(this);
    

    但是,这意味着当self 超出范围时,即在owner 的销毁期间,它将调用delete this。我们确实希望发生这种删除,否则this 将被删除两次(这是未定义的行为,实际上是段错误)。因此,我们还为self 的构造函数提供了一个实际上不会删除任何内容的Deleter。

    其余的代码应该用 cmets 不言自明。

    【讨论】:

    • 这听起来很酷,但我并不清楚如何对它们进行混叠!你能发布一个小例子吗?谢谢!
    • @StevenLu 我以前从未使用过别名,所以我必须了解细节,这实际上比我预期的要棘手。无论如何,付出努力是值得的,现在我对它们非常了解,并且有一个我正在与您分享的工作示例(请参阅编辑)。另一种方法是不使用这些别名,直接使用Ownershared_ptr&lt;Foo&gt; foo1_ptr 成员,使用Owner() : foo1_ptr(new Foo) {} 初始化,然后直接使用Observer observer(owner-&gt;foo1_ptr),但这会在堆上创建您可能想要避免的数据。
    • 我想知道是否有办法避免略显不合时宜的假删除器。忽略删除器会造成什么样的破坏?我猜它会双重释放类实例并使我们崩溃,因为 shared_ptr 旨在用于新分配的项目。 :( 虽然,也许我可以通过在一般情况下将其实现为基类,或者甚至更好的模板来隐藏这些技巧。
    • @StevenLu 是的,如果你不提供这个虚拟删除器,owner 会被删除两次,这会导致段错误(我确实尝试过)。这种“hack”似乎是唯一可能的解决方案,不涉及在堆上分配一些东西。实际上,如果堆上没有任何内容,则意味着没有调用new,因此也不应该调用delete。但是你确实需要一个非空地址的shared_ptr(才能使用weak_ptr),默认情况下它会调用delete,然后是segfault。 :-/ 感谢我网站上的评论 :-)
    【解决方案2】:

    只是一个更完整的工作 sn-p,显示 std::weak_ptr 动态,也许可以提供更多帮助。 (我特别喜欢这个 expired() 语义);

    #include<iostream>
    #include<memory>
    #include<string>
    
    class MessageProcessor{                
    };
    
    class Message{
    public:
    
            Message(std::shared_ptr<MessageProcessor>  _msg_proc, int _id){
                    proc_weak = std::weak_ptr<MessageProcessor>(_msg_proc);
                    proc_shared = _msg_proc;
                    id = _id;
            }
    
            std::weak_ptr<MessageProcessor> proc_weak;
            std::shared_ptr<MessageProcessor> proc_shared;        
            int id;
    };
    
    int main(){
    
            std::shared_ptr<MessageProcessor> proc(new MessageProcessor());
    
            Message msg(proc,1);
    
            // Here we have proc with 2 shared_ptr refs: 'proc' and 'msg.proc_shared'
            // As expected 'msg.proc_weak is not expired'
            if( !msg.proc_weak.expired() )
                    std::cout << "1) proc_weak is not EXPIRED. proc.use_count() == " << proc.use_count() << std::endl;        
    
            // make one of shared_ptr ref, point to other place
            msg.proc_shared = std::shared_ptr<MessageProcessor>();        
    
            // there is still the 'proc' reference        
            if( !msg.proc_weak.expired() )
                    std::cout << "2) proc_weak is not EXPIRED (yet). proc.use_count() == " << proc.use_count() << std::endl;
    
            // 'erase' the last reference
            proc = std::shared_ptr<MessageProcessor>(); 
    
            // Finally... There is no more refs in shared_pointer!
            if( msg.proc_weak.expired() )
                    std::cout << "3) proc_weak has EXPIRED. proc.use_count() == " << proc.use_count() << std::endl;
    
            return 0; 
    }
    

    【讨论】:

      猜你喜欢
      • 2012-12-12
      • 1970-01-01
      • 2011-08-17
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2021-10-10
      • 2015-10-27
      • 1970-01-01
      相关资源
      最近更新 更多