我开始研究c++ 11的智能指针,我没有看到std::weak_ptr有任何有用的用途。有人能告诉我什么时候std::weak_ptr是有用的/必要的吗?
当前回答
我看到了很多有趣的答案,解释引用计数等,但我错过了一个简单的例子,演示如何使用weak_ptr防止内存泄漏。在第一个例子中,我在循环引用的类中使用shared_ptr。当类超出作用域时,它们不会被销毁。
#include<iostream>
#include<memory>
using namespace std;
class B;
class A
{
public:
shared_ptr<B>bptr;
A() {
cout << "A created" << endl;
}
~A() {
cout << "A destroyed" << endl;
}
};
class B
{
public:
shared_ptr<A>aptr;
B() {
cout << "B created" << endl;
}
~B() {
cout << "B destroyed" << endl;
}
};
int main()
{
{
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->bptr = b;
b->aptr = a;
}
// put breakpoint here
}
如果你运行代码片段,你会看到类被创建,但没有被销毁:
A created
B created
现在我们把shared_ptr改成weak_ptr:
class B;
class A
{
public:
weak_ptr<B>bptr;
A() {
cout << "A created" << endl;
}
~A() {
cout << "A destroyed" << endl;
}
};
class B
{
public:
weak_ptr<A>aptr;
B() {
cout << "B created" << endl;
}
~B() {
cout << "B destroyed" << endl;
}
};
int main()
{
{
shared_ptr<A> a = make_shared<A>();
shared_ptr<B> b = make_shared<B>();
a->bptr = b;
b->aptr = a;
}
// put breakpoint here
}
这一次,当使用weak_ptr时,我们看到了正确的类破坏:
A created
B created
B destroyed
A destroyed
其他回答
另一个答案,希望更简单。(对谷歌员工)
假设您有Team和Member对象。
显然,这是一个关系:Team对象将拥有指向其成员的指针。成员也可能有一个指向他们的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 fail if the parent was freed since we stored weak_ptr
} else {
// do stuff
}
// tempParentSharedPtr is released when it goes out of scope
http://en.cppreference.com/w/cpp/memory/weak_ptr Std::weak_ptr是一个智能指针,它持有对Std::shared_ptr管理的对象的非所有(“弱”)引用。它必须转换为std::shared_ptr才能访问被引用的对象。
Std::weak_ptr建模临时所有权:当一个对象只有在它存在时才需要访问,并且它可能在任何时候被其他人删除时,Std::weak_ptr用于跟踪该对象,并将其转换为Std::shared_ptr以承担临时所有权。如果原始的std::shared_ptr在此时被销毁,对象的生命周期将被延长,直到临时的std::shared_ptr也被销毁。
此外,std::weak_ptr用于打破std::shared_ptr的循环引用。
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());
它们在Boost中很有用。当调用异步处理程序时,不能保证目标对象仍然存在。诀窍是使用std::bind或lambda capture将weak_ptr绑定到异步处理程序对象中。
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";
}
} );
}
这是在Boost中经常看到的self = shared_from_this()习惯用法的变体。Asio示例,其中挂起的异步处理程序不会延长目标对象的生命周期,但如果目标对象被删除,则仍然是安全的。
除了其他已经提到的有效用例,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字段之前,您需要线程通信并必须停止所有加载器线程。否则,加载器将继续加载,直到所有图像都完成—即使它们已经过时。在简化的示例中,这不会太难,但在实际环境中,事情可能要复杂得多。
The threads would probably be part of a thread pool used by multiple managers, of which some are being stopped, and some aren't etc. The simple parameter imagesToLoad would be a locked queue, into which those managers push their image requests from different control threads with the readers popping the requests - in an arbitrary order - at the other end. And so the communication becomes difficult, slow and error-prone. A very elegant way to avoid any additional communication in such cases is to use std::shared_ptr in conjunction with 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检查,如果拥有的指针没有过期。
推荐文章
- 为什么在标准容器中使用std::auto_ptr<>是错误的?
- 空括号的默认构造函数
- 做独一无二的完美转发
- 未定义对静态constexpr char的引用[]
- 什么是不归路?
- 在c++ 11中局部静态变量初始化是线程安全的吗?
- c++ 11中引入了哪些突破性的变化?
- auto&&告诉我们什么?
- 我如何调用::std::make_shared类只有保护或私有构造函数?
- 如何“=默认”不同于“{}”默认构造函数和析构函数?
- 什么是奇怪的重复模板模式(CRTP)?
- 如何自动转换强类型枚举为int?
- 在一个类中使用具有成员函数的泛型std::function对象
- 当启用c++ 11时,std::vector性能回归
- 什么时候使用哪种指针?