一、智能指针存在的意义
智能指针主要解决以下问题: (1)内存泄漏:内存手动释放,使用智能指针可以自动释放。 (2)共享所有权指针的传播和释放,比如多线程使用同一个对象时析构问题。
C++里面有四个智能指针:auto_ptr、share_ptr、unique_ptr、weak_ptr。其中后三个是C++11支持的,并且第一个已经在C++11弃用;所以,重点讲解share_ptr、unique_ptr、weak_ptr。 它们的特点: (1)unique_ptr独占对象的所有权,由于没有引用计数,性能较好。 (2)share_ptr共享对象的所有权,但性能略差。 (3)weak_ptr配合share_ptr,解决循环引用问题。
二、shared_ptr
std::shared_ptr使用引用计数,每一个shared_ptr的拷贝都指向相同的内存。在最后一个shared_ptr析构的时候,内存才会被释放。
sharedd_ptr共享被管理的对象,同一时刻可以有多个shared_ptr拥有对象的所有权,当最后一个shared_ptr对象销毁时,被管理对象自动销毁。
简单的说,shared_ptr实现包含了两个部分: (1)一个指向堆上创建的对象的裸指针 raw_ptr。 (2)一个指向内部隐藏的、共享的管理对象 shared_count_object。其中use_count是当前这个堆上对象被多少对象引用了,简单来说就是引用计数。
2.1、shared_ptr内存模型
图片
shared_ptr内部包含两个指针,一个指向对象,一个指向控制块。控制块包含一个引用计数、一个弱计数和其他数据(比如删除器、分配器等)。
其中reference count会累计对象的使用者数量。
代码语言:javascript
AI代码解释
std::shared_ptr<int> p1(new int(1)); std::shared_ptr<int> p2=p1;上例中,p1和p2的内存模型关系就是:
图片
2.2、shared_ptr使用场景
(1)使用智能指针可以自动释放占用的内存。
代码语言:javascript
AI代码解释
// Buffer对象分配在堆上,但能自动释放 shared_ptr<Buffer> buf=make_shared<Buffer>("auto free memory"); // Buffer对象分配在堆上,但需要手动delete释放 Buffer *buf2=new Buffer("free memory");(2)共享所有权指针的传播和释放。
图片
同样的数据,但不同的业务处理不一样。使用shared_ptr智能指针,可以减少内存拷贝,因为有引入计数的存在,当引入计数变为 0 时才真正去释放内存。
2.3、shared_ptr的基本使用和常用函数
(1)s.get():返回shared_ptr中保存的裸指针。 (2)s.reset(…):重置shared_ptr。
- reset()不带参数时,若智能指针s是唯一指向该对象的指针,则释放,并置空。若智能指针s不是唯一指向该对象的指针,则引用计数减一,同时将s置为空。
- reset()带参数时,若智能指针s是唯一指向该对象的指针,则释放并指向新的对象。若智能指针s不是唯一指向该对象的指针,则引用计数减一,并指向新的对象。
例如:
代码语言:javascript
AI代码解释
auto s=make_shared<int>(100); s.reset(new int(200));(3)s.use_count():返回shared_ptr的强引用计数。 (4)s.unique():若use_count为1返回true,否则返回false。
2.3.1、初始化 make_shared / reset
通过构造函数、std::shared_ptr辅助函数和reset方法来初始化shared_ptr,代码如下:
代码语言:javascript
AI代码解释
std::shared_ptr<int> p1(new int(1)); std::shared_ptr<int> p2=p1; std::shared_ptr<int> p3; p3.reset(new int(1));应该优先使用make_shared来构造智能指针,因为它更高效。
代码语言:javascript
AI代码解释
auto p1=make_shared<int>(100); shared_ptr<int> p2=make_shared<int>(100); // 相当于 shared_ptr<int> p1(new int(100));不能将原始指针直接赋给一个智能指针。例如,下面这种方法是错误的:
代码语言:javascript
AI代码解释
std::shared_ptr<int> p=new int(1);shared_ptr不能通过“直接将原始这种赋值”来初始化,需要通过构造函数和辅助方法来初始化。
- 对于一个未初始化的智能指针,可以通过reset方法来初始化;
- 当智能指针有值的时候调用reset会引起引用计数减1。
另外智能指针可以通过重载的bool类型操作符来判断。
代码语言:javascript
AI代码解释
#include <iostream> #include <memory> using namespace std; int main() { std::shared_ptr<int> p1; p1.reset(new int(1)); std::shared_ptr<int> p2 = p1; // 引用计数此时应该是2 cout << "p2.use_count() = " << p2.use_count()<< endl; p1.reset(); cout << "p1.reset()\n"; // 引用计数此时应该是2 cout << "p2.use_count()= " << p2.use_count() << endl; if(!p1) { cout << "p1 is empty\n"; } if(!p2) { cout << "p2 is empty\n"; } p2.reset(); cout << "p2.reset()\n"; cout << "p2.use_count()= " << p2.use_count() << endl; if(!p2) { cout << "p2 is empty\n"; } return 0; }2.3.2、获取原始指针 get()
当需要获取原始指针时,可以通过get方法来返回原始指针,代码如下所示:
代码语言:javascript
AI代码解释
std::shared_ptr<int> ptr(new int(1)); int *p = ptr.get(); // 不小心 delete p;谨慎使用get()的返回值,如果不清楚其危险性则永远不要调用get()函数。 p.get()的返回值就相当于一个裸指针的值,不合适的使用这个值,上述陷阱的所有错误都有可能发生, 遵守以下几个约定:
- 不要保存get()的返回值 ,无论是保存为裸指针还是shared_ptr都是错误的。
- 保存为裸指针不知什么时候就会变成空悬指针,保存为shared_ptr则产生了独立指针。
- 不要delete p.get()的返回值 ,会导致对一块内存delete两次的错误。
2.3.3、指定删除器
如果用shared_ptr管理非new对象或是没有析构函数的类时,应当为其传递合适的删除器。
代码语言:javascript
AI代码解释
#include <iostream> #include <memory> using namespace std; void DeleteIntPtr(int *p) { cout<< "Call DeleteIntPtr"<<endl; delete p; } int main(int argc, char **argv) { shared_ptr<int> p(new int(1),DeleteIntPtr); return 0; }当p的引用计数为0时,自动调用删除器DeleteIntPtr来释放对象的内存。删除器可以是一个lambda表达式,上面的写法可以改为:
代码语言:javascript
AI代码解释
shared_ptr<int> p(new int(1),[](int *p){ cout<< "Call DeleteIntPtr"<<endl; delete p; });当使用shared_ptr管理动态数组时,需要指定删除器,因为shared_ptr的默认删除器不支持数据对象,代码如下:
代码语言:javascript
AI代码解释
std::shared_ptr<int> p3(new int[10],[](int *p){delete [] p;});2.4、shared_ptr使用要注意的问题
(1)不要用一个原始指针初始化多个shared_ptr。 错误示范如下:
代码语言:javascript
AI代码解释
int *ptr=new int; shared_ptr<int> p1(ptr); shared_ptr<int> p2(ptr);//逻辑错误(2)不要在函数实参中创建shared_ptr。 错误示范如下:
代码语言:javascript
AI代码解释
function(shared_ptr<int>(new int),g());因为C++的函数参数的计算顺序在不同的编译器不同的约定下可能是不一样的,一般是从右到左,但也可能从左到右;所以,可能的过程是先new int,然后调用g(),如果恰好g()发生异常,而shared_ptr还没有创建,则int内存泄漏;正确的写法应该是先创建智能指针,代码如下:
代码语言:javascript
AI代码解释
shared_ptr<int> p1(new int); function(p1,g());(3)通过shared_from_this返回this指针。 不要将this指针作为shared_ptr返回回来,因为this指针本质上是一个裸指针,因此,可能会导致重复析构,如下例子:
代码语言:javascript
AI代码解释
#include <iostream> #include <memory> using namespace std; class MyClass { public: shared_ptr<MyClass> GetSelf() { return shared_ptr<MyClass>(this);//不要这样做 } MyClass() { cout << "MyClass()" << endl; }; ~MyClass() { cout << "~MyClass()" << endl; }; }; int main() { shared_ptr<MyClass> sp1(new MyClass); shared_ptr<MyClass> sp2 = sp1->GetSelf(); return 0; }运行后调用两次析构:
代码语言:javascript
AI代码解释
MyClass() ~MyClass() ~MyClass() free(): double free detected in tcache 2 已放弃 (核心已转储)在这个例子中,由于用同一个指针(this)构造了两个智能指针sp1和sp2,而他们之间是没有任何关系的,在离开作用域之后this将会被构造的两个智能指针各自析构,导致重复析构的错误。
正确返回this的shared_ptr的做法是:让目标类继承std::enable_shared_from_this类,然后使用基类的成员函数shared_from_this()返回this的shared_ptr,如下所示:
代码语言:javascript
AI代码解释
#include <iostream> #include <memory> using namespace std; class MyClass: public std::enable_shared_from_this<MyClass> { public: shared_ptr<MyClass> GetSelf() { return shared_from_this();//不要这样做 } MyClass() { cout << "MyClass()" << endl; }; ~MyClass() { cout << "~MyClass()" << endl; }; }; int main() { shared_ptr<MyClass> sp1(new MyClass); shared_ptr<MyClass> sp2 = sp1->GetSelf(); return 0; }执行结果:
代码语言:javascript
AI代码解释
MyClass() ~MyClass()(4)避免循环引用。 循环引用会导致内存泄漏。比如:
代码语言:javascript
AI代码解释
#include <iostream> #include <memory> using namespace std; class A; class B; class B { public: shared_ptr<A> aptr; B(); ~B(); private: }; B::B() { cout << "B()" << endl; } B::~B() { cout << "B is deleted" << endl; } class A { public: shared_ptr<B> bptr; A(); ~A(); private: }; A::A() { cout << "A()" << endl; } A::~A() { cout << "A is deleted" << endl; } int main() { shared_ptr<A> ap(new A); shared_ptr<B> bp(new B); ap->bptr = bp; bp->aptr = ap; cout << "main leave" << endl; // 循环引用导致ap bp退出了作用域都没有析构 return 0; }循环引用导致ap和bp的引用计数为2,在离开作用域之后,ap和bp的引用计数减为1,并不回减为0,导致两个指针都不会被析构,产生内存泄漏。 解决的办法是把A和B任何一个成员变量改为weak_ptr。
三、unique_ptr
(1)unique_ptr是一个独占型的智能指针,不能将其复制给另一个unique_ptr。 (2)unique_ptr可以指向一个数组。 (3)unique_ptr需要确定删除器的类型。
3.1、unique_ptr是一个独占型的智能指针
unique_ptr是一个独占型的智能指针,它不允许其他的智能指针共享其内部的指针,不允许通过赋值将一个unique_ptr赋值给另一个unique_ptr。下面的错误示例。
www.dongchedi.com/article/7599372466490016280
www.dongchedi.com/article/7599372650099999257
www.dongchedi.com/article/7599372851132776984
www.dongchedi.com/article/7599372306620039742
www.dongchedi.com/article/7599372306619908670
www.dongchedi.com/article/7599370698540368409
www.dongchedi.com/article/7599370149677842969
www.dongchedi.com/article/7599370761858023961
www.dongchedi.com/article/7599370557410378302
www.dongchedi.com/article/7599369686161244697
www.dongchedi.com/article/7599370201301500441
www.dongchedi.com/article/7599370761857761817
www.dongchedi.com/article/7599371529427960344
www.dongchedi.com/article/7599369580343001625
www.dongchedi.com/article/7599335913373680190
www.dongchedi.com/article/7599334077720150590
www.dongchedi.com/article/7599334457882984984
www.dongchedi.com/article/7599335431117259326
www.dongchedi.com/article/7599334113941701182
www.dongchedi.com/article/7599333889857307198
www.dongchedi.com/article/7599332892518580761
www.dongchedi.com/article/7599334507862786584
www.dongchedi.com/article/7599332232163131928
www.dongchedi.com/article/7599332409993544254
www.dongchedi.com/article/7599331431303103000
www.dongchedi.com/article/7599331421261939224
www.dongchedi.com/article/7599331283516801561
www.dongchedi.com/article/7599330823195591193
www.dongchedi.com/article/7599236014061912601
www.dongchedi.com/article/7599236952864326168
www.dongchedi.com/article/7599233838816461337
www.dongchedi.com/article/7599234108371354174
www.dongchedi.com/article/7599232673466499646
www.dongchedi.com/article/7599231381528871449
www.dongchedi.com/article/7599230852278911550
www.dongchedi.com/article/7599230129096933950
www.dongchedi.com/article/7599224698819592766
www.dongchedi.com/article/7599377947404861977