1. 智能指针——指针更安全的解决方案(一) 指针的功能比较强大,但单独使用指针存在一些问题,可能会导致野指针、内存泄露。 因此应该尽量使用两种方法,更加安全地使用指针:
使用更安全的指针——智能指针 ;
不使用指针,使用更安全的方式——引用 ;
智能指针需要使用头文件memory。
空指针的表示方法:原始版本中,用指针值NULL 代表空指针。但是由于NULL 不仅可以赋值给指针,还可以赋值给int等类型的元素,因此存在歧义。后来引入nullptr,用于代表空指针。
(1) auto_ptr(C++ 17中移除) auto_ptr:一种拥有严格对象所有权语义的智能指针。 特点:
指针超出作用域/生命周期结束之后,指向的空间会被自动释放(防止内存泄露);
对其他元素赋值/拷贝时,会发生所有权转移,剥夺原指针的控制权(变为nullptr)。
auto_ptr原理:在拷贝 / 赋值过程中,直接剥夺原对象对内存的控制权,转交给新对象,然后再将原对象指针置为nullptr(早期:NULL)。这种做法也叫管理权转移。 他的缺点不言而喻,当我们再次去访问原对象时,程序就会报错,所以auto_ptr可以说实现的不好,很多企业在其库内也是要求不准使用auto_ptr。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 int main() { {// 特点一:确定auto_ptr失效的范围,该括号外pl失效,同时指向的空间也被回收 // 对int使用 auto_ptr<int> pI(new int(10)); cout << *pI << endl; // 10 auto_ptr<string> languages[5] = { auto_ptr<string>(new string("C")), auto_ptr<string>(new string("Java")), auto_ptr<string>(new string("C++")), auto_ptr<string>(new string("Python")), auto_ptr<string>(new string("Rust")) }; cout << "There are some computer languages here first time: \n"; for (int i = 0; i < 5; ++i) { cout << *languages[i] << endl; } auto_ptr<string> pC; pC = languages[2]; // 特点二:languges[2] loses ownership. 将所有权从languges[2]转让给pC, //特点二:此时languges[2]不再引用该字符串从而变成空指针 cout << "There are some computer languages here second time: \n"; for (int i = 0; i < 2; ++i) { cout << *languages[i] << endl; } cout << "The winner is " << *pC << endl; //cout << "There are some computer languages here third time: \n"; //for (int i = 0; i < 5; ++i) //{ // cout << *languages[i] << endl; //} } return 0; }
(2) unique_ptr 特点:
指针超出作用域/生命周期结束之后,指向的空间会被自动释放(防止内存泄露)
unique_ptr是一块内存的专属指针,一般不允许被赋值和复制
可以通过move函数进行所有权转移,解决其他指针需要访问的问题。1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 int main() { // 特点一:在这个范围之外,unique_ptr指向的空间被自动释放 { auto i = unique_ptr<int>(new int(10)); cout << *i << endl; } // unique_ptr auto w = std::make_unique<int>(10); cout << *(w.get()) << endl; // 10 //auto w2 = w; // 特点二:编译错误,如果想要把 w 复制给 w2, 是不可以的。 // 因为复制从语义上来说,两个对象将共享同一块内存。 // 特点三:unique_ptr只 支持移动语义, 即如下 auto w2 = std::move(w); // w2 获得内存所有权,w 此时等于 nullptr cout << ((w.get() != nullptr) ? (*w.get()) : -1) << endl; // -1 cout << ((w2.get() != nullptr) ? (*w2.get()) : -1) << endl; // 10 }
所有智能指针的创建方法都有两种,以unique_ptr为例,下面两种方法都可以创建unique_ptr指针:
auto upw1(std::make_unique<Widget>());
std::unique_ptr<Widget> upw2(new Widget);
c++ 之智能指针:尽量使用std::make_unique和std::make_shared而不直接使用newhttps://blog.csdn.net/p942005405/article/details/84635673
(3) shared_ptr和weak_ptr 为了解决auto_ptr在对象所有权上的局限性,在使用引用计数的机制上提供了可以共享所有权的智能指针shared_ptr(当然这需要额外的开销)。
shared_ptr特点:
使用引用计数表示访问空间的指针数量。
通过引用计数和对象共享所有权(引用计数会记录共享的数量)。
当引用计数=0,表示没有指针使用,开始析构并释放空间。
此外,shared_ptr也支持move函数来转移所有权、也会在超出作用域/生命周期结束之后,自动释放指向的空间
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 int main() { // shared_ptr 的生命范围,超出后自动释放空间 { //shared_ptr 代表的是共享所有权,即多个 shared_ptr 可以共享同一块内存。 auto wA = shared_ptr<int>(new int(20)); { auto wA2 = wA; //wA2也是shared_ptr,和wA共享使用权 cout << ((wA2.get() != nullptr) ? (*wA2.get()) : -1) << endl;// 20 cout << ((wA.get() != nullptr) ? (*wA.get()) : -1) << endl; // 20 cout << wA2.use_count() << endl; // 引用计数,2 cout << wA.use_count() << endl; // 引用计数,2 } //cout << wA2.use_count() << endl; //wA2的声明周期已结束,指向的空间也已释放 cout << wA.use_count() << endl; // 1 cout << ((wA.get() != nullptr) ? (*wA.get()) : -1) << endl; // 20 //shared_ptr 内部是利用引用计数来实现内存的自动管理,每当复制一个 shared_ptr, // 引用计数会 + 1。当一个 shared_ptr 离开作用域时,引用计数会 - 1。 // 当引用计数为 0 的时候,则 delete 内存。 } // shared_ptr的move 语法 auto wAA = std::make_shared<int>(30); auto wAA2 = std::move(wAA); // 此时 wAA 等于 nullptr,wAA2.use_count() 等于 1 cout << ((wAA.get() != nullptr) ? (*wAA.get()) : -1) << endl; // -1 cout << ((wAA2.get() != nullptr) ? (*wAA2.get()) : -1) << endl; // 30 cout << wAA.use_count() << endl; // 0 cout << wAA2.use_count() << endl; // 1 //将 wAA 对象 move 给 wAA2,意味着 wAA 放弃了对内存的所有权和管理,此时 wAA对象等于 nullptr。 //而 wAA2 获得了对象所有权,但因为此时 wAA 已不再持有对象,因此 wAA2 的引用计数为 1。 return 0; }
但shared_ptr同时产生了问题:循环引用
共享指针循环引用
于是引入指针weak_ptr ,依附于shared_ptr,和它一起协同工作,采用一种观察者的模式(不会对shared_ptr造成直接影响),两者地位并不对等 。
weak_ptr特点:
像“粉丝”一样只进行观察,不对shared_ptr造成直接影响;
只能对shared_ptr进行引用;
当shared_ptr失效后,对应的weak_ptr也失效。
weak_ptr解决循环引用的问题
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 struct B; //先声明,这样struct A中才可以定义该类型成员 struct A { shared_ptr<B> pb; ~A(){cout << "~A()" << endl;} }; struct B { shared_ptr<A> pa; ~B(){cout << "~B()" << endl;} }; // pa 和 pb 存在着循环引用,根据 shared_ptr 引用计数的原理,pa 和 pb 都无法被正常的释放。 // weak_ptr 是为了解决 shared_ptr 双向引用的问题。 struct BW; //先声明,这样struct AW中才可以定义该类型成员 struct AW { shared_ptr<BW> pb; ~AW(){cout << "~AW()" << endl;} }; struct BW { weak_ptr<AW> pa; ~BW(){cout << "~BW()" << endl;} }; //产生循环引用 void Test() { cout << "Test shared_ptr and shared_ptr: " << endl; shared_ptr<A> tA(new A()); //生成一个指针指向结构体A,结构体内部有一个B的指针 shared_ptr<B> tB(new B()); //生成一个指针指向结构体B,结构体内部有一个A的指针 cout << tA.use_count() << endl; // 1 cout << tB.use_count() << endl; // 1 tA->pb = tB; //tA指向结构体A,A内部的指针指向B tB->pa = tA; //tB指向结构体B,B内部的指针指向A cout << tA.use_count() << endl; // 2 除了tA外,tB->pa也指向A cout << tB.use_count() << endl; // 2 除了tB外,tA->pb也指向B } //解决了循环引用 void Test2() { cout << "Test weak_ptr and shared_ptr: " << endl; shared_ptr<AW> tA(new AW()); shared_ptr<BW> tB(new BW()); cout << tA.use_count() << endl; // 1 cout << tB.use_count() << endl; // 1 tA->pb = tB; tB->pa = tA; cout << tA.use_count() << endl; // 1,因为weak_ptr不被计数 cout << tB.use_count() << endl; // 2 } int main() { Test(); Test2(); return 0; }
最终会看到,只有结构体AW、BW执行了析构函数,因为循环引用导致了无法自动释放空间。
2. 引用——指针更安全的解决方案(二) 引用本质是一种不允许修改指向的指针 ,变量的引用可以认为是变量的别名。 Java中貌似没有指针,但实际上如果没有指针,它的功能会大大削弱。Java实际上是通过引用来间接使用指针的。从某种角度上来说,Java是介于C和C++之间的语言。
使用指针有哪些坑:
空指针
野指针
不知不觉改变了指针的值,却继续使用;
使用引用,则可以:
不存在空引用;
必须初始化;
一个引用永远指向它初始化的那个对象;
有了指针为什么还需要引用? Bjarne Stroustrup(C++之父)的解释: 为了支持函数运算符重载;有了引用为什么还需要指针? Bjarne Stroustrup(C++之父)的解释: 为了兼容C语言; 注意:C语言完全用指针,JAVA语言完全用引用。
补充 关于函数传递参数类型的说明:
对内置基础类型 (如int,double等)而言,在函数中传递pass by value 更高效;
对OO面向对象中自定义类型 而言,在函数中传递时pass by reference to const 更高效。