C++学习笔记----6、内存管理(五)---- 智能指针(4)
3、weak_ptr
在C++中还有一个与shared_ptr相关的智能指针叫做weak_ptr。weak_ptr可以包含一个被shared_ptr管理的资源的引用。weak_ptr自身不拥有资源,所以shared_ptr不被禁止释放资源。当weak_ptr被破坏时(如当其不在活动范围内),weak_ptr不破坏指向的资源;然而,它可以用于决定资源是否被相关shared_ptr释放。weak_ptr的构造函数要求shared_ptr或者另一个weak_ptr作为参数。要访问存储在weak_ptr中的指针,需要将其转化为shared_ptr。有两种方式:
- 在weak_ptr实例上使用lock()成员函数,它返回一个shared_ptr。如果weak_ptr同时被释放的话它就会返回一个nullptr。
- 生成一个新的shared_ptr实例,将weak_ptr作为参数传递给shared_ptr构造函数。如果weak_ptr与之相联的shared_ptr被释放的话就会抛出一个std::bad_weak_ptr的例外。
以下的示例展示的weak_ptr的用法:
import std;
using namespace std;
class Simple
{
public:
Simple() { println("Simple constructor called!"); }
~Simple() { println("Simple destructor called!"); }
};
void useResource(weak_ptr<Simple>& weakSimple)
{
auto resource{ weakSimple.lock() };
if (resource) {
println("Resource still alive.");
}
else {
println("Resource has been freed!");
}
}
int main()
{
auto sharedSimple{ make_shared<Simple>() };
weak_ptr<Simple> weakSimple{ sharedSimple };
// Try to use the weak_ptr.
useResource(weakSimple);
// Reset the shared_ptr.
// Since there is only 1 shared_ptr to the Simple resource, this will
// free the resource, even though there is still a weak_ptr alive.
sharedSimple.reset();
// Try to use the weak_ptr a second time.
useResource(weakSimple);
}
结果如下:
Simple constructor called!
Resource still alive.
Simple destructor called!
Resource has been freed!
weak_ptr也支持C风格的数组,与shared_ptr一样。
4、传参给函数
只有在传递属主或者属主共享存在的时候,能够接受指针作为其参数也应该接受智能指针。为了共享shard_ptr的属主,只接受通过值的shared_ptr作为参数。同样的,为了传递unique_ptr的属主,只接受通过值的unique_ptr作为参数。后者要求使用move的语法,我们以后再讨论。
如果既没有属主传递也没有属主共享介入,函数只有reference-to-non-const或者 reference-to-const参数指向其背后的资源,或者是一个原始指南指向它,如果nullptr是有效参数的话。像const shared_ptr<T>& 或者const unique_ptr<T>&这样的参数类型是没有什么道理的。
5、函数返回值
标准智能指针shared_ptr,unique_ptr,以及weak_ptr通过值可以容易且有效地从函数返回,得益于强制与非强制的拷贝省略以及移动的语法。移动的语法目前并不重要。重要的是这些方式从函数中返回一个智能指针都是高效的。例如,可以写出如下的create()函数,在main()中如示例使用:
import std;
using namespace std;
class Simple
{
public:
Simple() { println("Simple constructor called!"); }
~Simple() { println("Simple destructor called!"); }
};
unique_ptr<Simple> create()
{
auto ptr{ make_unique<Simple>() };
// Do something with ptr...
return ptr;
}
int main()
{
unique_ptr<Simple> mySmartPtr1{ create() };
auto mySmartPtr2{ create() };
}
6、enable_shared_from_this
从std::enable_shared_from_this继承一个类允许成员函数被在一个安全返回shared_ptr或者weak_ptr给自身的对象调用。没有这个基类,一种方式就是返回一个有效的shared_ptr或者weak_ptr给this,通过给类加一个weak_ptr作为成员,返回其复本或者返回由其构造的shared_ptr。enable_shared_from_this类添加了如下的两个成员函数给其继承的类:
- shared_from_this():返回一个共享对象属主的shared_ptr。
- weak_from_this():返回一个跟踪对象属主的weak_ptr。
这个高级属性我们不进行详细讨论,但是下面的代码展示了其用途。share_from_this()与weak_from_this()为公共成员函数。然而,你可能会发现from_this部分在公共接口时有点令人搞不懂,只是作为一个展示,下面的Foo类定义了它自身的成员函数getPointer():
import std;
using namespace std;
class Foo : public enable_shared_from_this<Foo>
{
public:
shared_ptr<Foo> getPointer() {
return shared_from_this();
}
};
int main()
{
auto ptr1{ make_shared<Foo>() };
auto ptr2{ ptr1->getPointer() };
}
注意:只有在指针已经被保存在shared_ptr时才可以在对象上使用shared_from_this();否则的话,就会抛出bad_weak_ptr的例外。在上面的例子中,make_shared()在main()中用于生成一个叫做ptr1的shared_ptr,它包含一个Foo的实例。shared_ptr生成之后,允许在Foo实例上调用shared_from_this()。另一方面,调用weak_from_this()总是可以的,只是如果在一个没有存储在shared_ptr的指针上的对象进行调用就可能会返回空的weak_ptr。
下面的代码是对getPointer()成员函数的彻头彻尾的错误应用:
class Foo
{
public:
shared_ptr<Foo> getPointer() {
return shared_ptr<Foo>(this);
}
};
如果像刚展示的那样在main()中使用同样的代码,对Foo的应用就会产生双重删除。两个完全独立的shared_ptr(ptr1与ptr2)指向了同一个对象,都会在其不在活动范围时尝试去删除该对象。
7、智能指针与C风格的函数的互操作性(c++23特性)
通常情况下,C风格的函数使用返回类型来显示函数是否正确执行或者是否有错误。既然返回类型早就用于报告错误,那另外的输出参数就用于函数返回其他的数据。例如:
using errorcode = int;
errorcode my_alloc(int value, int** data)
{
*data = new int{ value };
println("Allocated");
return 0;
}
errorcode my_free(int* data)
{
delete data;
println("Freed");
return 0;
}
这种C风格的API,my_alloc()函数返回了一个errorcode,在输出的data参数中返回了分配的数据。在c++23之前,不能在my_alloc()中直接使用智能指针,比如unique_ptr。那你可以这样做:
unique_ptr<int, decltype(&my_free)> myIntSmartPtr(nullptr, my_free);
int* data{ nullptr };
my_alloc(42, &data);
myIntSmartPtr.reset(data);
这个也比较容易吧。c++23引入了std::out_ptr与inout_ptr()函数来帮助做这些,定义在<memory>中,使用该函数,上面的代码片断就可以写得更优雅:
unique_ptr<int, decltype(&my_free)> myIntSmartPtr(nullptr, my_free);
my_alloc(42, inout_ptr(myIntSmartPtr));
如果你确信传递给inout_ptr的指针为nullptr,也可以使用out_ptr。
8、旧的已经移除的auto_ptr
旧的,c++11之前的标准库包含了一个智能指针的基本实现,叫做auto_ptr。很不幸,auto_ptr有很严重的缺点。其中一个就是当在比如vector这样的标准库构造函数中使用时不能正确工作。c++官方废除了auto_ptr,从c++17开始,从标准库中完全移除掉了。它被unique_ptr与shared_ptr替换掉了。这里提到auto_ptr是为了确认你了解这一点并且 确认不会用到它。
不要使用旧的auto_ptr智能指针!要使用unique_ptr,如果需要共享属主就使用shared_ptr。