(1)智能指针是为了解决内存泄漏问题
(2)内存泄漏问题,本质上是因为程序员自己忘记主动释放导致的
(3)智能指针的解决思路是:申请内存使用完后并自动释放
(1)一方面,利用局部变量/对象分配在栈上,代码段结束时会自动释放的特性
(2)另一方面,利用对象释放时会自动调用析构函数的特性
(1)定义一个类TestPtr,写出构造函数和析构函数 (2)main.cpp中写一个全局函数func,在main中调用执行func (3)func中定义一个TestPtr类的对象作为局部变量,测试验证程序执行后自动调用析构函数 (4)在TestPtr中定义成员变量int *p, 构造中new申请空间给p,析构中delete释放 (5)在TestPtr中写test_use函数,使用p所指向的内存 (6)分析以上,TestPtr使用了动态内存但是并不会导致内存泄漏,这就是智能指针的套路
#include <iostream> using namespace std; class TestPtr { private: int *p;//用于指向申请的动态内存 public: TestPtr() { cout << "TestPtr()" << endl; p = NULL; } TestPtr(int size) { cout << "TestPtr(int size)" << endl; p = new int[size]; } ~TestPtr() { cout << "~TestPtr()" << endl; if(NULL != p) delete[] p; } void testprint(void); }; void TestPtr::testprint(void) { if (NULL != p) { p[0] = 12; cout << "p[0] = " << p[0] << endl; } else { cout << "p = NULL" << endl; } } void UseTest(void) { TestPtr tp(5); tp.testprint(); } int main(int argc, char *argv[]) { UseTest(); return 0; }
(1)智能指针本身是一个类,而普通指针本身是一个变量
(2)智能指针的“智能”指的是随着智能指针对象本身的释放,所指向的对象也释放
(3)C++里面的四个智能指针: auto_ptr, unique_ptr,shared_ptr, weak_ptr 其中后三个是C++11支持,并且第一个已经被C++11弃用
参考学习:https://zh.cppreference.com/w/cpp/memory
参考学习:https://zh.cppreference.com/w/cpp/memory/auto_ptr
#include <iostream> #include <memory> using namespace std; class people { private: string name; public: people(){}; people(string s) { name = s; cout << "people(string s)" << endl; } ~people() { //虚构函数内部一般可能会有动态内存的释放,虽然我们这里未提供 cout << "~people()" << endl; } void print(void) { cout << "name = " << this->name << endl; } }; int main(int argc, char *argv[]) { //示例1,不适用智能指针时 people p("linux1");//定义people类的一个对象p p.print(); people *p1 = new people("linux1");//定义一个指向 people类的一个对象 的指针 p1->print(); delete p1;//需要手动进行释放,否则会发生内存泄漏,通过调试可知,若无该语句,则不会执行析构函数 //示例二:使用智能指针实现上述示例 auto_ptr<people> p2(new people("linux2")); p2->print(); auto_ptr<people> p3 = p2; p3->print(); //p2->print();//这句不隐掉,则会出现段错误,因为经过赋值后,p2已经失去了对他指向的那个 //操作的权力,若p2、p3都能操作的话,则会出现段错误,因为二者结束时都会调用 //析构函数,而内存释放只可进行一次,再次释放就会报错,因为已经释放掉了 p3.reset(new people("harmonyos"));//将被指向的对象从linux2换位harmonyos,替换时会先 p3->print(); //释放掉linux2 people *p4 = p3.release();//p3释放掉自己所指向的那个对象,无法再进行操作 //p3->print();//释放后无法操作,会报段错误 p4->print(); delete p4;//经过上面操作,现在使用的不是指针了,需要我们手动进行内存的释放 return 0; }
参考学习(必看):https://blog.csdn.net/czc1997/article/details/84026887
使用unique_ptr如果不传deleter,则使用的就是默认的(析构函数),这就和auto_ptr是一样的,但如果提供了deleter则与前面的情况不同,对应于上边图片中两个不同的unique_ptr重载
参考学习: https://zh.cppreference.com/w/cpp/memory/unique_ptr https://zh.cppreference.com/w/cpp/memory/unique_ptr/unique_ptr(重难点) 注:本专栏博客提供关于C++手册的网址链接,由于服务器在国外,所以访问速度比较慢, 有时电脑无法打开,可尝试在手机上使用手机流量打开网址(亲试有效),当然你要是会翻 墙就另说了
#include <iostream> #include <memory> struct Foo { // 要管理的对象 Foo() { std::cout << "Foo ctor\n"; }//构造函数 Foo(const Foo&) { std::cout << "Foo copy ctor\n"; }//拷贝构造函数 Foo(Foo&&) { std::cout << "Foo move ctor\n"; }//移动构造函数 ~Foo() { std::cout << "~Foo dtor\n"; }//析构函数 }; struct D { // 删除器 D() {};//默认构造函数 D(const D&) { std::cout << "D copy ctor\n"; }//拷贝构造函数 D(D&) { std::cout << "D non-const copy ctor\n";}//非const型的拷贝构造函数 D(D&&) { std::cout << "D move ctor \n"; }//移动构造函数 void operator()(Foo* p) const {//函数对象 //在这里除了调用delete还可以增加一些其他的设计和封装,这也是 //unique_ptr和auto_ptr不同的地方 std::cout << "D is deleting a Foo\n"; delete p; }; }; int main() { std::cout << "Example constructor(1)...\n"; std::unique_ptr<Foo> up1; // up1 为空,定义一个空指针,并未绑定对象,故不会调用构造函数 std::unique_ptr<Foo> up1b(nullptr); // up1b 为空,同上 std::cout << "Example constructor(2)...\n"; { std::unique_ptr<Foo> up2(new Foo); // up2 现在占有 Foo,绑定了对象调用构造函数 } // Foo 的作用域结束,其被删除,在{}这个代码块内 std::cout << "Example constructor(3)...\n"; D d; { // 删除器类型不是引用 std::unique_ptr<Foo, D> up3(new Foo, d); // 复制删除器 } { // 删除器类型是引用 std::unique_ptr<Foo, D&> up3b(new Foo, d); // up3b 保有到 d 的引用,故不需要调用构造函数 } std::cout << "Example constructor(4)...\n"; { // 删除器不是引用 std::unique_ptr<Foo, D> up4(new Foo, D()); // 移动删除器,D():调用构造函数,对应移动语义 } std::cout << "Example constructor(5)...\n"; { std::unique_ptr<Foo> up5a(new Foo); std::unique_ptr<Foo> up5b(std::move(up5a)); // 所有权转移 } std::cout << "Example constructor(6)...\n"; { std::unique_ptr<Foo, D> up6a(new Foo, d); // 复制 D std::unique_ptr<Foo, D> up6b(std::move(up6a)); // 移动 D std::unique_ptr<Foo, D&> up6c(new Foo, d); // D 是引用 std::unique_ptr<Foo, D> up6d(std::move(up6c)); // 复制 D } #if (__cplusplus < 201703L) std::cout << "Example constructor(7)...\n"; { std::auto_ptr<Foo> up7a(new Foo); std::unique_ptr<Foo> up7b(std::move(up7a)); // 所有权转移 } #endif std::cout << "Example array constructor...\n"; { std::unique_ptr<Foo[]> up(new Foo[3]); } // 删除三个 Foo 对象 } 输出: Example constructor(1)... Example constructor(2)... Foo ctor ~Foo dtor Example constructor(3)... Foo ctor D copy ctor D is deleting a Foo ~Foo dtor Foo ctor D is deleting a Foo ~Foo dtor Example constructor(4)... Foo ctor D move ctor D is deleting a Foo ~Foo dtor Example constructor(5)... Foo ctor ~Foo dtor Example constructor(6)... Foo ctor D copy ctor D move ctor Foo ctor D non-const copy ctor D is deleting a Foo ~Foo dtor D is deleting a Foo ~Foo dtor Example constructor(7)... Foo ctor ~Foo dtor Example array constructor... Foo ctor Foo ctor Foo ctor ~Foo dtor ~Foo dtor ~Foo dtor
参考学习:https://zh.cppreference.com/w/cpp/memory/unique_ptr
这些方法的使用与auto_ptr相似。
#include <iostream> #include <memory> using namespace std; struct Foo { void bar(){std::cout << "Foo::bar\n";} }; void f(const Foo& foo) { cout << "f(const Foo& foo)" << endl; } int main(int argc, char *argv[]) { //示例一:单对象版本的元素访问 unique_ptr<Foo> p(new Foo); p->bar(); (*p).bar(); f(*p); //示例二:数组版本的元素访问 const int size = 10; unique_ptr<int[]> fact(new int[size]); for(int i = 0; i < 5; i++) { fact[i] = (i==0) ? : i * fact[i-1]; } for(int i = 0; i < 5; i++) { cout << i << " : " << fact[i] << "\n"; } return 0; }
(1)消费unique_ptr的函数能以值或以右值引用接收它
unique_ptr<People> pass_through(unique_ptr<People> p) { p->bar(); return p; }
(2)unique_ptr在智能指针变量赋值方面比auto_ptr安全
unique_ptr<string> pu1(new string ("hello world")); unique_ptr<string> pu2; pu2 = pu1; // 不允许这样操作 unique_ptr<string> pu3; pu3 = unique_ptr<string>(new string ("You")); // 允许
(1)不要与裸指针混用
int *x(new int()); unique_ptr<int> up1,up2; up1.reset(x); up2.reset(x); //会使up1 up2指向同一个普通对象,将来程序结束时会导致内存二次释放,出现报错
(2)不能忘记接收u.release()方法的返回值
(1)unique_ptr 之所以叫这个名字,是因为它只能指向一个对象,当它指向新的对象时,之前所指向的对象会被释放。
(2)当unique_ptr本身被释放时,指向的对象也会被自动释放
(3)unique_ptr实现独占式拥有或严格拥有概念,保证同一时间内只有一个智能指针可以指向该对象。
(4)要安全的重用unique_ptr,可以用reset给他赋新值。
(5)std::move()能够将一个unique_ptr赋给另一个。这样转移所有权后 还是有可能出现原有指针调用就崩溃的情况。但是这个语法能强调你是在转移所有权,让你清晰的知道自己在做什么,从而不乱调用原有指针。
(6)C++ 11中最常用的智能指针类型为shared_ptr,它采用引用计数的方法,记录当前内存资源被多少个智能指针引用。对shared_ptr进行初始化时不能将一个普通指针直接赋值给智能指针,因为一个是指针,一个是类
注:本文章参考了《朱老师物联网大讲堂》课程笔记,并结合了自己的实际开发经历以及网上他人的技术文章,综合整理得到。如有侵权,联系删除!水平有限,欢迎各位在评论区交流。