我开始研究c++ 11的智能指针,我没有看到std::weak_ptr有任何有用的用途。有人能告诉我什么时候std::weak_ptr是有用的/必要的吗?
当前回答
除了其他已经提到的有效用例,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::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 << "weak1 value is " << *tmp << '\n';
else
std::cout << "weak1 is expired\n";
// weak2 points to new data (5)
if(auto tmp = weak2.lock())
std::cout << "weak2 value is " << *tmp << '\n';
else
std::cout << "weak2 is expired\n";
}
输出
weak1 is expired
weak2 value is 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());
除了其他已经提到的有效用例,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检查,如果拥有的指针没有过期。
当我们不想拥有对象时:
Ex:
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, both指针的use_count是2。
当shared_ptr超出作用域时,计数仍然保持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中使用weak_ptr而不是shared_ptr,这更有意义。
缓存就是一个很好的例子。
对于最近访问的对象,您希望将它们保存在内存中,因此可以保留一个指向它们的强指针。定期扫描缓存,确定最近没有访问哪些对象。你不需要把它们保存在内存中,所以你去掉强指针。
但是,如果该对象正在使用,而其他一些代码持有指向它的强指针,该怎么办?如果缓存删除了指向该对象的唯一指针,就再也找不到它了。因此,缓存保留了一个弱指针,指向它需要找到的对象,如果它们碰巧留在内存中。
这正是弱指针所做的——它允许你在一个对象仍然在附近时定位它,但如果没有其他东西需要它,它就不会保留它。
推荐文章
- Shared_ptr转换为数组:应该使用它吗?
- c++ 11中的递归lambda函数
- c++ auto关键字。为什么它是魔法?
- Std::auto_ptr改为Std::unique_ptr
- 什么是“参数依赖查找”(又名ADL,或“Koenig查找”)?
- 公共朋友交换成员函数
- 下面这些短语在c++中是什么意思:0 -,default-和value-initialization?
- 现代c++能让你免费获得性能吗?
- c++11返回值优化或移动?
- 函数标题中的箭头操作符(->)
- c++中“using”关键字背后的逻辑是什么?
- 我如何类型定义一个函数指针与c++ 11使用语法?
- cbegin/cend背后的原因是什么?
- 如何在Eclipse CDT中启用c++ 11/ c++ 0x支持?
- 如何停止c++控制台应用程序从退出立即?