文章目录
1. 智能指针模板类
1.1 使用智能指针
-
智能指针是行为类似于指针的类对象,可帮助管理动态内存分配。
-
这三个智能指针模板(auto_ptr、unique_ptr和shared_ptr)都定义了类似指针的对象,可以将new获得(直接或间接)的地址赋给这种对象。当智能指针过期时,其析构函数将使用delete来释放内存。因此,如果将new返回的地址赋给这些对象,将无需记住稍后释放这些内存:在智能指针过期时,这些内存将自动被释放。
-
如果指针ps是对象,则可以在对象过期时,让它的析构函数删除指向的内存。这正是auto_ptr、unique_ptr和shared_ptr背后的思想。模板auto_ptr是C++98提供的解决方案,C++11已将其摒弃,并提供了另外两种解决方案。
1.2 C++11的智能指针
-
unique_ptr
不允许多个指针共享资源,可以用标准库中的move函数转移指针;
-
shared_ptr
多个指针共享资源;
-
weak_ptr
可复制shared_ptr,但其构造或者释放对资源不产生影响。
1.3 有关智能指针的注意事项
两个常规指针指向同一个string对象,是不可接受的,因为程序将试图删除同一对象两次,要避免这种问题,方法有多种:
-
定义赋值运算符,使之执行深度复制。这样两个指针将指向不同的对象,其中一个对象是另一个对象的副本;
-
建立所有权概念(ownership),对于特定的对象,只能有一个智能指针可拥有它,这样只有拥有对象的智能指针的构造函数会删除该对象。然后,让赋值操作转让所有权。这就是auto_ptr和unique_ptr的策略。但unique_ptr比auto_ptr更严格。
std::auto_ptr<string> p1(new string("auto")); //#1 std::auto_ptr<string> p2; //#2 p2 = p1; //#3 std::unique_ptr<string> p3(new string("auto")); //#4 std::auto_ptr<string> p4; //#5 p4 = p3; //#6
注1:p1所有权被剥夺后,如程序试图再使用p1,将导致程序崩溃。编译器认为语句#6非法,避免了p3不再指向有效数据的问题。因此,unique_ptr比auto_ptr更安全。
注2:程序试图将一个unique_ptr赋给另一个时,如果源unique_ptr是个临时右值(很快被销毁,没有机会使用它来访问无效的数据),编译器允许这样做;如果源unique_ptr将存在一段时间,编译器将禁止这样做。
注3:相比于auto_ptr,unique_ptr还有一个优点,有一个可用于数组的变体。必须将delete和new配对,将delete[]和new[]配对。模板auto_ptr使用delete而不是delete[],因此只能与new一起使用,不能与new[]一起使用。但unique_ptr有使用new[]和delete[]的版本。
-
创建智能更高的指针,跟踪引用特定对象的智能指针数。这称为引用计数(reference counting)。赋值时计数将加1,而指针过期时计数将减1。仅当最后一个指针过期时(引用计数为0),才调用delete。这是shared_ptr采用的策略。
-
使用new分配内存时,才能使用auto_ptr和shared_ptr,使用new[]分配内存时,不能使用它们。不使用new分配内存时,不能使用auto_ptr或shared_ptr。
-
不使用new和new[]分配内存时,不能使用unique_ptr。
1.4 选择智能指针
-
如果程序要使用多个指向同一个对象的指针,应选择shared_ptr;很多STL算法都支持复制和赋值操作,这些操作可用于shared_ptr,而不能用于unique_ptr(编译器发出警告)和auto_ptr(行为不确定)。
-
如果程序不需要多个指向同一个对象的指针,则可使用unique_ptr;如果函数使用new分配内存,并返回指向该内存的指针,将其返回类型声明为unique_ptr是不错的选择;这样所有权将转让给接受返回值得unique_ptr,而该智能指针将负责调用delete。
-
模板shared_ptr包含一个显式构造函数,可用于将右值unique_ptr转换为shared_ptr。shared_ptr将接管原来归unique_ptr所有的对象。
1.5 unique_ptr的操作
-
使用原始指针创建unique_ptr对象:要创建非空的unique_ptr对象,需要在创建对象时在其构造函数中传递原始指针。
-
使用 std::make_unique创建unique_ptr对象/C++14: std::make_unique<>()是C++14引入的新函数。
-
创建一个空的unique_ptr对象:创建一个空的unique_ptr对象,因为没有与之关联的原始指针,所以它是空的。
-
释放关联的原始指针:在unique_ptr对象上调用 release()将释放其关联的原始指针的所有权,并返回原始指针。这里是释放所有权,并没有delete原始指针,reset()会delete原始指针。
-
重置 unique_ptr 对象:在unique_ptr对象上调用reset()函数将重置它,即它将释放delete关联的原始指针并使unique_ptr 对象为空。
-
获取被管理对象的指针:使用get()·函数获取管理对象的指针。
std::unique_ptr<Task> taskPtr2(new Task(55));
std::unique_ptr<Task> taskPtr6 = std::make_unique<Task>(88);
1.6 weak_ptr的操作
weak_ptr可以从一个shared_ptr 或者另一个对weak_ptr象构造,获得对象的观测权,但不管理对象,它的构造不会引起指针引用计数的增加。
weak_ptr是为了配合shared_ptr而引入的一种智能指针,因为它不具有普通指针的行为,没有重载operator*和->,它的最大作用在于协助shared_ptr工作,像旁观者那样观测资源的使用情况。
使用weak_ptr的成员函数use_count()可以观测资源的引用计数,另一个成员函数expired()的功能等价于use_count()==0,但更快,表示被观测的资源(也就是shared_ptr的管理的资源)已经不复存在。weak_ptr可以使用一个非常重要的成员函数lock()从被观测的shared_ptr获得一个可用的shared_ptr对象, 从而操作资源。但当expired()==true的时候,lock()函数将返回一个存储空指针的shared_ptr。
使用场景
弱引用特性,不拥有对象,只有延迟到尝试调用lock()时才会有可能临时拥有对象:
- 只是持有一个没有拥有权的被shared_ptr 托管的对象。
- 只有调用lock()创建shared_ptr 指针时才会引用实际对象。
- 在lock()成功时会延长shared_ptr 对象的生命周期,因为它递增了一个引用计数。
- 在shared_ptr 主指针结束后,如果成功的指针还存在weak_ptr,那么这时候还有lock()的代码调用,引用计数仍然递增。
2. std::make_shared
- C++11 中引入了智能指针, 同时还有一个模板函数 std::make_shared 可以返回一个指定类型的 std::shared_ptr。与 std::shared_ptr 的构造函数相比有以下优点:提高性能,异常安全。
std::shared_ptr<Widget> spw(new Widget);
- 这段代码需要分配两次内存。每个std::shared_ptr都指向一个控制块,控制块包含被指向对象的引用计数以及其他东西。控制块内存是在std::shared_ptr的构造函数中分配的。故直接使用new,需要一块内存分配给Widget,另一块内存分配给控制块。
auto spw = std::make_shared<Widget>();
- 一次分配就足够了。这是因为std::make_shared申请一个单独的内存块来同时存放Widget对象和控制块。这个优化减少了程序的静态大小,因为代码只包含一次内存分配的调用,并且这会加快代码的执行速度,因为内存只分配了一次。另外,使用std::make_shared消除了一些控制块需要记录的信息,这样潜在地减少了程序的总内存占用。
#define _CRT_SECURE_NO_WARNINGS
#include <iostream>
#include <stdio.h>
#include <string>
#include <memory>
#include <vector>
#include <Eigen/Core>
typedef Eigen::Matrix<float, 3, 1> Vector3f;
typedef Eigen::Matrix<int, 3, 1> Vector3d;
void check(std::weak_ptr<int> &wp)
{
std::shared_ptr<int> sp = wp.lock(); // 转换为shared_ptr<int>
if (sp != nullptr)
{
std::cout << "still: " << *sp << std::endl;
}
else
{
std::cout << "still: " << "pointer is invalid" << std::endl;
}
}
void mytest()
{
std::shared_ptr<int> sp1(new int(22));
std::shared_ptr<int> sp2 = sp1;
std::weak_ptr<int> wp = sp1; // 指向shared_ptr<int>所指对象
std::cout << "count: " << wp.use_count() << std::endl; // count: 2
std::cout << *sp1 << std::endl; // 22
std::cout << *sp2 << std::endl; // 22
check(wp); // still: 22
sp1.reset();
std::cout << "count: " << wp.use_count() << std::endl; // count: 1
std::cout << *sp2 << std::endl; // 22
check(wp); // still: 22
sp2.reset();
std::cout << "count: " << wp.use_count() << std::endl; // count: 0
check(wp); // still: pointer is invalid
return;
}
int main()
{
auto spw = std::make_shared<int>();
std::cout<<"spw location : " << spw << std::endl;
mytest();
system("pause");
std::cout<< std::endl;
return 0;
}
3.智能指针循环引用问题及解决办法
如图Person对象和Car对象互相引用,导致代码执行结束后两者引用计数都不为0,导致内存不能释放,出现内存泄漏。
循环引用的解决办法就是使用weak_ptr.
右侧代码执行结束后,Person对象引用计数为1,Car对象引用计数为2;若代码执行结束后,car指针先释放,Car对象引用计数减1变为1,person指针释放后,Person对象引用计数减1变为0,Person对象释放内存,成员对象m_car也得到释放,Car对象引用指针减1变为0,Car对象也释放了内存。
参考链接:https://blog.csdn.net/weixin_45880571/article/details/119345415