如果是的std :: weak_ptr的有用吗?(When is std::weak_ptr use

2019-06-17 19:16发布

我开始学习C ++的11智能指针,我看不出任何有用的使用std::weak_ptr 。 谁能告诉我什么时候std::weak_ptr是有用的/有必要吗?

Answer 1:

一个很好的例子将是一个高速缓存。

对于最近访问对象,你要保留在内存中,所以你抱持着强烈的指向他们。 定期,您扫描缓存,并决定哪些对象没有被最近访问。 你并不需要保留那些记忆,让你摆脱强烈的指针。

但是,如果该对象正在使用什么和其他一些代码拥有强大的指针呢? 如果缓存摆脱它的唯一指针的对象,它永远不能再次找到它。 因此,高速缓存保持一个弱指针的对象,它需要找到,如果他们碰巧留在内存中。

这正是一个弱指针做什么 - 它可以让你找到一个对象,如果它仍然是围绕,但如果没有别的需要它不把它周围。



Answer 2:

std::weak_ptr是解决一个非常好的方式悬摆指针问题。 只要使用原始指针也不可能知道所引用的数据已释放或没有。 取而代之的是,通过让std::shared_ptr管理数据,并且提供std::weak_ptr对数据的用户,用户可以通过调用检查数据的有效性expired()lock()

你无法做到这一点std::shared_ptr孤单,因为所有std::shared_ptr实例共享未的所有实例之前删除的数据的所有权std::shared_ptr被删除。 下面是如何检查使用悬挂指针的示例lock()

#include <iostream>
#include <memory>

int main()
{
    // OLD, problem with dangling pointer
    // PROBLEM: ref will point to undefined data!

    int* ptr = new int(10);
    int* ref = ptr;
    delete ptr;

    // NEW
    // SOLUTION: check expired() or lock() to determine if pointer is valid

    // empty definition
    std::shared_ptr<int> sptr;

    // takes ownership of pointer
    sptr.reset(new int);
    *sptr = 10;

    // get pointer to data without taking ownership
    std::weak_ptr<int> weak1 = sptr;

    // deletes managed object, acquires new pointer
    sptr.reset(new int);
    *sptr = 5;

    // get pointer to new data without taking ownership
    std::weak_ptr<int> weak2 = sptr;

    // weak1 is expired!
    if(auto tmp = weak1.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak1 is expired\n";

    // weak2 points to new data (5)
    if(auto tmp = weak2.lock())
        std::cout << *tmp << '\n';
    else
        std::cout << "weak2 is expired\n";
}


Answer 3:

另一个答案,希望简单。 (供同道的Google)

假设你有TeamMember对象。

显然,这是一个关系: Team对象将指向它的Members 。 它很可能是成员也将有一个回指针到他们的Team目标。

然后,你必须依赖周期。 如果您使用shared_ptr ,对象将不再自动当你放弃对他们的参考,因为他们以循环方式相互引用释放。 这是一个内存泄漏。

您可以通过使用打破这种weak_ptr 。 “所有者”通常使用shared_ptr和“拥有”使用weak_ptr到其父,并暂时将其转换为shared_ptr ,当它需要访问它的父。

存储弱PTR:

weak_ptr<Parent> parentWeakPtr_ = parentSharedPtr; // automatic conversion to weak from shared

然后在需要时使用它

shared_ptr<Parent> tempParentSharedPtr = parentWeakPtr_.lock(); // on the stack, from the weak ptr
if( !tempParentSharedPtr ) {
  // yes it may failed if parent was freed since we stored weak_ptr
} else {
  // do stuff
}
// tempParentSharedPtr is released when it goes out of scope


Answer 4:

这里有一个例子,通过@jleahy给我:假设你有任务的集合,通过异步执行,以及管理std::shared_ptr<Task> 。 您可能需要做定期的任务什么的,所以计时器事件可能穿越std::vector<std::weak_ptr<Task>> ,给的任务事做。 然而,同时任务可能同时决定,不再需要和死亡。 因此,定时器可以检查任务是否仍是通过使从弱指针的共享指针和使用共享指针活着,只要它不为空。



Answer 5:

weak_ptr也很好,检查对象的正确缺失-尤其是在单元测试。 典型的使用情况可能是这样的:

std::weak_ptr<X> weak_x{ shared_x };
shared_x.reset();
BOOST_CHECK(weak_x.lock());
... //do something that should remove all other copies of shared_x and hence destroy x
BOOST_CHECK(!weak_x.lock());


Answer 6:

当你不能保证被调用的异步处理程序时,一个目标对象仍然存在,他们与Boost.Asio的有用。 诀窍是一个结合weak_ptr到asynchonous处理程序对象,使用std::bind或λ捕获。

void MyClass::startTimer()
{
    std::weak_ptr<MyClass> weak = shared_from_this();
    timer_.async_wait( [weak](const boost::system::error_code& ec)
    {
        auto self = weak.lock();
        if (self)
        {
            self->handleTimeout();
        }
        else
        {
            std::cout << "Target object no longer exists!\n";
        }
    } );
}

这是一个变体self = shared_from_this()如果目标对象被删除成语常见于Boost.Asio的示例中,在一个未处理的异步处理程序将延长所述目标对象的寿命,但仍是安全的。



Answer 7:

shared_ptr的 :拥有真正的对象。

weak_ptr的 :使用lock连接到真正的主人或否则返回null。

粗略地说, weak_ptr作用类似于房屋中介机构的作用。 如果没有代理,以获得租金,我们可能要检查随机的房子在城市的房子。 这些代理确保我们只访问目前仍在出租访问和使用这些房子。



Answer 8:

当使用指针来了解可用的不同类型的指针是非常重要的,当它是有道理的使用每一个。 有两大类四种类型的指针,如下所示:

  • 原始指针:
    • 原始指针[即SomeClass* ptrToSomeClass = new SomeClass(); ]
  • 智能指针:
    • 唯一指针[即std::unique_ptr<SomeClass> uniquePtrToSomeClass ( new SomeClass() ); ]
    • 共享指针[即std::shared_ptr<SomeClass> sharedPtrToSomeClass ( new SomeClass() ); ]
    • 弱指针[即std::weak_ptr<SomeClass> weakPtrToSomeWeakOrSharedPtr ( weakOrSharedPtr ); ]

原始指针(有时被称为“传统的指针”,或“C指针”)提供“准系统”指针行为和是错误和内存泄漏的公共源极。 原始指针不提供用于跟踪资源和开发商的所有权必须手动调用“删除”,以确保它们不会产生内存泄漏。 如果资源是共享的,因为它可以是具有挑战性的知道任何对象是否仍然指向资源这变得困难。 由于这些原因,原始指针通常应该避免,且只能在有限的范围的代码的性能关键部分中使用。

独特的指针是一个基本的智能指针是“拥有”底层的原始指针的资源和负责调用删除并释放一旦“拥有”的唯一指针超出范围的对象分配内存。 “独一无二”的名称指的是只有一个对象可能“自己”在给定时间点的唯一指针。 所有权可被转移到通过所述移动命令另一个目的,但唯一的指针无法被复制,或共享。 由于这些原因,独特的指针是一个很好的选择,以在只有一个对象需要的指针在给定的时间的情况下裸指针,而这种缓解从需要释放内存的开发商在拥有对象的生命周期结束。

共享指针是另一种类型的智能指针相类似的唯一指针,而是允许许多对象有过共享指针的所有权。 就像唯一指针,共享指针是负责释放所分配的内存一旦所有对象都做了指向资源。 它达到了这个标准的技术叫做引用计数。 每当一个新的对象需要共享指针的引用计数递增一的所有权。 类似地,当对象超出范围或停止指向资源时,引用计数减一。 当引用计数为零,分配的内存被释放。 由于这些原因,共享的指针是智能指针的一个非常强大的类型,应随时使用多个对象需要指向相同的资源。

最后,弱指针是,而不是直接指向一个资源,它们指向另一个指针(弱或共享的)智能指针的另一种类型。 弱指针不能直接访问的对象,但他们可以告诉对象是否仍然存在,或者如果它已过期。 弱指针可以被暂时转换为智能指针来访问指向的对象(只要它仍然存在)。 为了说明这一点,考虑下面的例子:

  • 你很忙,有重叠的会议:会议A和会议乙
  • 你决定去会议A和您的同事去会议乙
  • 你告诉你的同事,如果会议B被还在会议A结束后,你将加入
  • 以下两种情况下可以发挥出来:
    • 会议上结束,会议B的还在,所以你加入
    • 会议上结束,会议B已经也结束了,所以你不加入

在这个例子中,你有一个微弱的指针会议B.你是不是一个“所有者”在会议B上可没有你结束,你不知道它是否结束与否,除非你检查。 如果还没有结束,你可以加入和参与,否则,你不能。 这比具有共享指针会议B,因为你会然后在两个会议A和会议B(同时参与两个)的“所有者”不同。

这个例子说明一个弱指针是如何工作的,当一个对象需要是一个外部观察者是有用的,但不希望拥有的责任。 这是在两个对象需要指向彼此(又名循环引用)方案是特别有用的。 随着共享指针,没有一个对象可以被释放,因为他们仍然是“强烈”被其他指向的对象。 弱指针,对象可以在需要时访问,并释放时,他们不再有存在的必要。



Answer 9:

除了其他已经提到的有效使用情况std::weak_ptr是在多线程环境中的真棒工具,因为

  • 它并不拥有的对象,因此不能在不同的线程阻碍缺失
  • std::shared_ptr与结合std::weak_ptr是针对悬摆指针安全的-在相反std::unique_ptr与原始指针一起
  • std::weak_ptr::lock()是一个原子操作(也见关于weak_ptr的线程安全 )

考虑一个任务,同时加载一个目录(〜10.000)的所有图像到内存中(例如,作为缩略图缓存)。 显然,要做到这一点的最好办法是控制线程,处理和管理图像和多个工作线程,它加载图像。 现在,这是一件容易的事。 这里是一个非常简单的实现( join()等被省略,线程就必须在实际的执行等不同的处理方式)

// a simplified class to hold the thumbnail and data
struct ImageData {
  std::string path;
  std::unique_ptr<YourFavoriteImageLibData> image;
};

// a simplified reader fn
void read( std::vector<std::shared_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageData : imagesToLoad )
     imageData->image = YourFavoriteImageLib::load( imageData->path );
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::shared_ptr<ImageData>>> splitDatas = 
        splitImageDatas( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

但是,如果你要中断的图像,例如加载,因为用户已经选择了不同的目录时,它会变得更加复杂。 或者即使你想摧毁经理。

你需要线程的通信和必须停止所有装载机线程,之前你可能会改变你的m_imageDatas场。 否则,装载机将在装载进行,直到所有图像都做了 - 即使他们已经过时了。 在简化的例子,这将不会太难,但在实际环境中的东西可以更复杂。

该线很可能是由多个管理人员,其中一些被用于停止一个线程池的一部分,有些不是等简单的参数imagesToLoad将是一个锁定的队列,在其中的经理把他们的形象从要求不同的控制螺纹与读者大跌眼镜的要求 - 以任意顺序 - 在另一端。 这样一来,沟通变得困难,缓慢且容易出错。 一个非常优雅的方式来避免在这种情况下,任何额外的通信使用std::shared_ptr会同std::weak_ptr

// a simplified reader fn
void read( std::vector<std::weak_ptr<ImageData>> imagesToLoad ) {
   for( auto& imageDataWeak : imagesToLoad ) {
     std::shared_ptr<ImageData> imageData = imageDataWeak.lock();
     if( !imageData )
        continue;
     imageData->image = YourFavoriteImageLib::load( imageData->path );
   }
}

// a simplified manager
class Manager {
   std::vector<std::shared_ptr<ImageData>> m_imageDatas;
   std::vector<std::unique_ptr<std::thread>> m_threads;
public:
   void load( const std::string& folderPath ) {
      std::vector<std::string> imagePaths = readFolder( folderPath );
      m_imageDatas = createImageDatas( imagePaths );
      const unsigned numThreads = std::thread::hardware_concurrency();
      std::vector<std::vector<std::weak_ptr<ImageData>>> splitDatas = 
        splitImageDatasToWeak( m_imageDatas, numThreads );
      for( auto& dataRangeToLoad : splitDatas )
        m_threads.push_back( std::make_unique<std::thread>(read, dataRangeToLoad) );
   }
};

这个实现是几乎与第一个一样容易,不需要任何额外的线程通信,并且可以在一个真正实现一个线程池/队列的一部分。 由于过期的图像会被跳过,未到期的图像进行处理,线程永远不会有正常的操作过程中被停止。 你总是可以安全地更改路径或破坏你的经理,因为读者FN检查,如果拥有指针没有过期。



Answer 10:

http://en.cppreference.com/w/cpp/memory/weak_ptr的std ::的weak_ptr是一个智能指针,其保持非所属(“弱”)参考到由STD :: shared_ptr的管理的对象。 它必须被转换为std :: shared_ptr的以访问引用的对象。

的std :: weak_ptr的机型临时所有权:当需要一个对象,如果它的存在仅被访问,并且它可以在任何时候被别人删除的std :: weak_ptr的用于跟踪的对象,并将其转换为标准: :shared_ptr的承担临时所有权。 如果原来的std :: shared_ptr的是在这个时候被破坏,对象的生命周期延长,直至临时的std :: shared_ptr的被销毁。

此外,性病:: weak_ptr的是用来打破的std :: shared_ptr的循环引用。



Answer 11:

有共享指针的缺点:shared_pointer不能处理亲子循环依赖。 如果父类使用共享指针使用子类的对象,在同一文件中,如果子类使用父类的对象的装置。 共享指针将无法破坏的所有对象,甚至共享指针完全不调用循环依赖的情况析构函数。 基本上共享指针不支持的引用计数机制。

这个缺点,我们可以克服使用weak_pointer。



Answer 12:

当我们不希望自己的对象:

例如:

class A
{
    shared_ptr<int> sPtr1;
    weak_ptr<int> wPtr1;
}

在上面的类wPtr1不拥有由wPtr1指向的资源。 如果资源被删除了,然后wPtr1已过期。

为了避免循环依赖:

shard_ptr<A> <----| shared_ptr<B> <------
    ^             |          ^          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
    |             |          |          |
class A           |     class B         |
    |             |          |          |
    |             ------------          |
    |                                   |
    -------------------------------------

现在,如果我们做的B类和A的shared_ptr的,两个指针的use_count为两个。

当shared_ptr离开OD范围计数仍1,因此A和B的对象不会被删除。

class B;

class A
{
    shared_ptr<B> sP1; // use weak_ptr instead to avoid CD

public:
    A() {  cout << "A()" << endl; }
    ~A() { cout << "~A()" << endl; }

    void setShared(shared_ptr<B>& p)
    {
        sP1 = p;
    }
};

class B
{
    shared_ptr<A> sP1;

public:
    B() {  cout << "B()" << endl; }
    ~B() { cout << "~B()" << endl; }

    void setShared(shared_ptr<A>& p)
    {
        sP1 = p;
    }
};

int main()
{
    shared_ptr<A> aPtr(new A);
    shared_ptr<B> bPtr(new B);

    aPtr->setShared(bPtr);
    bPtr->setShared(aPtr);

    return 0;  
}

输出:

A()
B()

A和B指针不会被删除,因而内存泄漏,因为我们可以从输出看到。

为了避免这样的问题,在A级,而不是shared_ptr的,这使得更多的意义只是使用了weak_ptr。



Answer 13:

我看到std::weak_ptr<T>作为句柄 std::shared_ptr<T>它让我得到std::shared_ptr<T>如果它仍然存在,但不会延长其寿命。 有几种方案时考虑到这样一点是有用的:

// Some sort of image; very expensive to create.
std::shared_ptr< Texture > texture;

// A Widget should be able to quickly get a handle to a Texture. On the
// other hand, I don't want to keep Textures around just because a widget
// may need it.

struct Widget {
    std::weak_ptr< Texture > texture_handle;
    void render() {
        if (auto texture = texture_handle.get(); texture) {
            // do stuff with texture. Warning: `texture`
            // is now extending the lifetime because it
            // is a std::shared_ptr< Texture >.
        } else {
            // gracefully degrade; there's no texture.
        }
    }
};

另一个重要的情况是打破周期中的数据结构。

// Asking for trouble because a node owns the next node, and the next node owns
// the previous node: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > next;
    std::shared_ptr< Node > prev;
};

// Asking for trouble because a parent owns its children and children own their
// parents: memory leak; no destructors automatically called.
struct Node {
    std::shared_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::shared_ptr< Node > next;
    std::weak_ptr< Node > prev;
};

// Better: break dependencies using a std::weak_ptr (but not best way to do it;
// see Herb Sutter's talk).
struct Node {
    std::weak_ptr< Node > parent;
    std::shared_ptr< Node > left_child;
    std::shared_ptr< Node > right_child;
};

香草萨特有极好的谈话 ,解释的语言功能的最佳使用(在这种情况下,智能指针),以确保自由泄漏默认情况下 (意为:家居点击在施工地方;你很难搞砸了)。 这是一个必须注意。



文章来源: When is std::weak_ptr useful?