使用std::weak_ptr进行共享资源所有权

3

我想知道如何使用C++11编写代码(最好是与向后兼容的智能指针类型boost或TR1一起使用),实现以下情况:

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

模型是InputSender持有对InputConsumers的引用列表,其中将有许多个InputConsumer。

ModelController可能没有、一个或多个InputConsumer,并且可能会有许多ModelController。但InputSender不会知道这些情况。

以下是期望达到的效果:以一种方式跟踪InputSender分配给它的InputConsumers,使其自己能够确定单个InputConsumer是否有效。似乎weak_ptr非常适合此目的,因为它们的使用需要检查此条件。

如果InputSender停止跟踪任何weak_ptr引用,则不会发生任何错误,相应的InputConsumer只会经历无线电静默。

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

因此,问题是,使用shared_ptr和weak_ptr是否适合此情况?我想知道shared_ptr是否完全适用,因为InputConsumer在概念上是由其ModelController拥有的,因此它们应该是成员变量。我不知道仅通过shared_ptr来管理它们对于ModelController是否有多大意义。我无法确定unique_ptr是否与weak_ptr配合使用。我应该只在ModelController的构造函数/析构函数中管理shared_ptr吗?

还可能存在一个众所周知(但我不知道!)的设计模式,可以将其归入其中,如果有人知道,请告诉我。

2个回答

1
我对共享指针没有太多专业知识,但是,这似乎是weak_ptr非常适合的用法。
在这种情况下,你只是很烦恼:
  1. 您想直接将InputConsumer作为ModelController的成员使用,因为它是一种微不足道的所有权关系。
  2. 您被迫使用shared_ptr才能与weak_ptr配合使用。
我认为,通过使用shared_ptr作为成员对象的别名来解决这个问题。根据C++.com的说法:

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

我从未尝试过,但这似乎适合你的情况:
  • InputConsumer作为ModelController的成员
  • 为每个成员设置别名shared_ptr
  • InputSender中使用weak_ptr引用它们

编辑

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

#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

棘手的部分是能够创建一个指向owner->foo1(对于foo2也是同样)的weak_ptr。为此,我们首先需要一个别名为owner->foo1shared_ptr。这只能通过以下方式完成:

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

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

shared_ptr<Owner> self(this);

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

其余代码应该很容易理解,有注释说明。


这听起来很酷,但我不清楚如何给它们取别名!你能发一个小例子吗?谢谢! - Steven Lu
@StevenLu 我以前从未使用过别名,所以我不得不学习细节,实际上比我预期的要棘手。无论如何,这是值得努力的,现在我很好地理解它们,并且有一个工作示例与您分享(请参见编辑)。另一种选择是不使用这些别名,并直接使用 Ownershared_ptr<Foo> foo1_ptr 成员进行初始化,即 Owner() : foo1_ptr(new Foo) {},然后直接使用 Observer observer(owner->foo1_ptr),但这会在堆上创建数据,您可能希望避免这种情况。 - Boris Dalstein
我在想是否有办法避免稍微不太合适的假删除器。如果忽略删除器会造成什么样的混乱呢?我猜它会双重释放类实例并导致崩溃,因为shared_ptr应该用于新分配的项目。:( 不过,也许我可以通过将其实现为基类的一般情况,或者甚至更好的模板来隐藏这种技巧性。 - Steven Lu
@StevenLu 是的,如果您不提供这个虚拟删除器,“owner”会被删除两次,导致段错误(我已经尝试过)。这个“hack”似乎是唯一可能的解决方案,它不涉及在堆上分配任何东西。确实,如果没有任何东西在堆上,那么就意味着没有调用new,因此也不应该调用delete。但是您确实需要一个指向非空地址的shared_ptr(以便能够使用weak_ptr),并且默认情况下它会调用delete,然后导致段错误。:-/ 感谢您在我的网站上发表评论 :-) - Boris Dalstein

0

再来一个完整的工作代码片段,展示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; 
}

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