C++ 智能指针 - 全部用法详解
Posted cpp_learner
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了C++ 智能指针 - 全部用法详解相关的知识,希望对你有一定的参考价值。
为什么要学习智能指针?
咳咳,这个问题不是问大家的,是询问我自己的!
我依稀记得刚离校出来找实习工作那会,去面试一份工作,其中有一个环节需要答题;有一道题目就是问什么是智能指针?卧槽?当时我就懵逼,智能指针我压根就没有听说过…
最后,面试的这份工作理所应当的黄了。
差不多是一年前左右吧,现在趁有闲余时间,学习一下智能指针,丰富一下自己!
目录
一、为什么要使用智能指针
一句话带过:智能指针就是帮我们C++程序员管理动态分配的内存的,它会帮助我们自动释放new出来的内存,从而避免内存泄漏!
如下例子就是内存泄露的例子:
#include <iostream>
#include <string>
#include <memory>
using namespace std;
// 动态分配内存,没有释放就return
void memoryLeak1() {
string *str = new string("动态分配内存!");
return;
}
// 动态分配内存,虽然有些释放内存的代码,但是被半路截胡return了
int memoryLeak2() {
string *str = new string("内存泄露!");
// ...此处省略一万行代码
// 发生某些异常,需要结束函数
if (1) {
return -1;
}
delete str; // 虽然写了释放内存的代码,但是遭到函数中段返回,使得指针没有得到释放
return 1;
}
int main(void) {
memoryLeak1();
memoryLeak2();
return 0;
}
memoryLeak1函数中,new了一个字符串指针,但是没有delete就已经return结束函数了,导致内存没有被释放,内存泄露!
memoryLeak2函数中,new了一个字符串指针,虽然在函数末尾有些释放内存的代码delete str,但是在delete之前就已经return了,所以内存也没有被释放,内存泄露!
使用指针,我们没有释放,就会造成内存泄露。但是我们使用普通对象却不会!
思考:如果我们分配的动态内存都交由有生命周期的对象来处理,那么在对象过期时,让它的析构函数删除指向的内存,这看似是一个 very nice 的方案?
智能指针就是通过这个原理来解决指针自动释放的问题!
- C++98 提供了 auto_ptr 模板的解决方案
- C++11 增加unique_ptr、shared_ptr 和weak_ptr
二、auto_ptr
auto_ptr 是c++ 98定义的智能指针模板,其定义了管理指针的对象,可以将new 获得(直接或间接)的地址赋给这种对象。当对象过期时,其析构函数将使用delete 来释放内存!
用法:
头文件: #include < memory >
用 法: auto_ptr<类型> 变量名(new 类型)
例 如:
auto_ptr< string > str(new string(“我要成为大牛~ 变得很牛逼!”));
auto_ptr<vector< int >> av(new vector< int >());
auto_ptr< int > array(new int[10]);
例:
我们先定义一个类,类的构造函数和析构函数都输出一个字符串用作提示!
定义一个私有成员变量,赋值20.
再定义一个私有成员方法用于返回这个私有成员变量。
class Test {
public:
Test() { cout << "Test的构造函数..." << endl; }
~Test() { cout << "Test的析构函数..." << endl; }
int getDebug() { return this->debug; }
private:
int debug = 20;
};
当我们直接new这个类的对象,却没有释放时。。。
int main(void) {
Test *test = new Test;
return 0;
}
可以看到,只是打印了构造函数这个字符串,而析构函数的字符却没有被打印,说明并没有调用析构函数!这就导致了内存泄露!
解决内存泄露的办法,要么手动delete,要么使用智能指针!
使用智能指针:
// 定义智能指针
auto_ptr<Test> test(new Test);
智能指针可以像普通指针那样使用:
cout << "test->debug:" << test->getDebug() << endl;
cout << "(*test).debug:" << (*test).getDebug() << endl;
这时再试试:
int main(void) {
//Test *test = new Test;
auto_ptr<Test> test(new Test);
cout << "test->debug:" << test->getDebug() << endl;
cout << "(*test).debug:" << (*test).getDebug() << endl;
return 0;
}
自动调用了析构函数。
为什么智能指针可以像普通指针那样使用???
因为其里面重载了 * 和 -> 运算符, * 返回普通对象,而 -> 返回指针对象。
具体原因不用深究,只需知道他为什么可以这样操作就像!
函数中返回的是调用get()方法返回的值,那么这个get()是什么呢?
智能指针的三个常用函数:
-
get() 获取智能指针托管的指针地址
// 定义智能指针 auto_ptr<Test> test(new Test); Test *tmp = test.get(); // 获取指针返回 cout << "tmp->debug:" << tmp->getDebug() << endl;
但我们一般不会这样使用,因为都可以直接使用智能指针去操作,除非有一些特殊情况。
函数原型:_NODISCARD _Ty * get() const noexcept { // return wrapped pointer return (_Myptr); }
-
release() 取消智能指针对动态内存的托管
// 定义智能指针 auto_ptr<Test> test(new Test); Test *tmp2 = test.release(); // 取消智能指针对动态内存的托管 delete tmp2; // 之前分配的内存需要自己手动释放
也就是智能指针不再对该指针进行管理,改由管理员进行管理!
函数原型:_Ty * release() noexcept { // return wrapped pointer and give up ownership _Ty * _Tmp = _Myptr; _Myptr = nullptr; return (_Tmp); }
-
reset() 重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉
// 定义智能指针 auto_ptr<Test> test(new Test); test.reset(); // 释放掉智能指针托管的指针内存,并将其置NULL test.reset(new Test()); // 释放掉智能指针托管的指针内存,并将参数指针取代之
reset函数会将参数的指针(不指定则为NULL),与托管的指针比较,如果地址不一致,那么就会析构掉原来托管的指针,然后使用参数的指针替代之。然后智能指针就会托管参数的那个指针了。
函数原型:void reset(_Ty * _Ptr = nullptr) { // destroy designated object and store new pointer if (_Ptr != _Myptr) delete _Myptr; _Myptr = _Ptr; }
使用建议:
-
尽可能不要将auto_ptr 变量定义为全局变量或指针;
// 没有意义,全局变量也是一样 auto_ptr<Test> *tp = new auto_ptr<Test>(new Test);
-
除非自己知道后果,不要把auto_ptr 智能指针赋值给同类型的另外一个 智能指针;
auto_ptr<Test> t1(new Test); auto_ptr<Test> t2(new Test); t1 = t2; // 不要这样操作...
-
C++11 后auto_ptr 已经被“抛弃”,已使用unique_ptr替代!C++11后不建议使用auto_ptr。
-
auto_ptr 被C++11抛弃的主要原因
1). 复制或者赋值都会改变资源的所有权
// auto_ptr 被C++11抛弃的主要原因 auto_ptr<string> p1(new string("I'm Li Ming!")); auto_ptr<string> p2(new string("I'm age 22.")); cout << "p1:" << p1.get() << endl; cout << "p2:" << p2.get() << endl; // p2赋值给p1后,首先p1会先将自己原先托管的指针释放掉,然后接收托管p2所托管的指针, // 然后p2所托管的指针制NULL,也就是p1托管了p2托管的指针,而p2放弃了托管。 p1 = p2; cout << "p1 = p2 赋值后:" << endl; cout << "p1:" << p1.get() << endl; cout << "p2:" << p2.get() << endl;
2). 在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制和可赋值
vector<auto_ptr<string>> vec; auto_ptr<string> p3(new string("I'm P3")); auto_ptr<string> p4(new string("I'm P4")); // 必须使用std::move修饰成右值,才可以进行插入容器中 vec.push_back(std::move(p3)); vec.push_back(std::move(p4)); cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl; // 风险来了: vec[0] = vec[1]; // 如果进行赋值,问题又回到了上面一个问题中。 cout << "vec.at(0):" << *vec.at(0) << endl; cout << "vec[1]:" << *vec[1] << endl;
访问越界了!
3). 不支持对象数组的内存管理
auto_ptr<int[]> array(new int[5]); // 不能这样定义
所以,C++11用更严谨的unique_ptr 取代了auto_ptr!
测试代码:
#include <iostream>
#include <string>
#include <memory>
#include <vector>
using namespace std;
class Test {
public:
Test() { cout << "Test的构造函数..." << endl; }
~Test() { cout << "Test的析构函数..." << endl; }
int getDebug() { return this->debug; }
private:
int debug = 20;
};
// 不要定义为全局变量,没有意义
//auto_ptr<Test> test(new Test);
void memoryLeak1() {
//Test *test = new Test;
// 定义智能指针
auto_ptr<Test> test(new Test);
cout << "test->debug:" << test->getDebug() << endl;
cout << "(*test).debug:" << (*test).getDebug() << endl;
// get方法
Test *tmp = test.get(); // 获取指针返回
cout << "tmp->debug:" << tmp->getDebug() << endl;
// release方法
Test *tmp2 = test.release(); // 取消智能指针对动态内存的托管
delete tmp2; // 之前分配的内存需要自己手动释放
// reset方法:重置智能指针托管的内存地址,如果地址不一致,原来的会被析构掉
test.reset(); // 释放掉智能指针托管的指针内存,并将其置NULL
test.reset(new Test()); // 释放掉智能指针托管的指针内存,并将参数指针取代之
// 忠告:不要将智能指针定义为指针
//auto_ptr<Test> *tp = new auto_ptr<Test>(new Test);
// 忠告:不要定义指向智能指针对象的指针变量
//auto_ptr<Test> t1(new Test);
//auto_ptr<Test> t2(new Test);
//t1 = t2;
return;
}
int memoryLeak2() {
//Test *test = new Test();
// 定义智能指针
auto_ptr<Test> test(new Test);
// ...此处省略一万行代码
// 发生某些异常,需要结束函数
if (1) {
return -1;
}
//delete test;
return 1;
}
int main1(void) {
//memoryLeak1();
//memoryLeak2();
//Test *test = new Test;
//auto_ptr<Test> test(new Test);
//cout << "test->debug:" << test->getDebug() << endl;
//cout << "(*test).debug:" << (*test).getDebug() << endl;
auto_ptr 被C++11抛弃的主要原因
//auto_ptr<string> p1(new string("I'm Li Ming!"));
//auto_ptr<string> p2(new string("I'm age 22."));
//
//cout << "p1:" << p1.get() << endl;
//cout << "p2:" << p2.get() << endl;
//p1 = p2;
//cout << "p1 = p2 赋值后:" << endl;
//cout << "p1:" << p1.get() << endl;
//cout << "p2:" << p2.get() << endl;
// 弊端2.在STL容器中使用auto_ptr存在着重大风险,因为容器内的元素必须支持可复制
vector<auto_ptr<string>> vec;
auto_ptr<string> p3(new string("I'm P3"));
auto_ptr<string> p4(new string("I'm P4"));
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;
// 风险来了:
vec[0] = vec[1];
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;
// 弊端3.不支持对象数组的内存管理
//auto_ptr<int[]> array(new int[5]); // 不能这样定义
return 0;
}
三、unique_ptr
auto_ptr是用于C++11之前的智能指针。由于 auto_ptr 基于排他所有权模式:两个指针不能指向同一个资源,复制或赋值都会改变资源的所有权。auto_ptr 主要有三大问题:
- 复制和赋值会改变资源的所有权,不符合人的直觉。
- 在 STL 容器中使用auto_ptr存在重大风险,因为容器内的元素必需支持可复制(copy constructable)和可赋值(assignable)。
- 不支持对象数组的操作
以上问题已经在上面体现出来了,下面将使用unique_ptr解决这些问题。
所以,C++11用更严谨的unique_ptr 取代了auto_ptr!
unique_ptr 和 auto_ptr用法几乎一样,除了一些特殊。
unique_ptr特性
- 基于排他所有权模式:两个指针不能指向同一个资源
- 无法进行左值unique_ptr复制构造,也无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
- 保存指向某个对象的指针,当它本身离开作用域时会自动释放它指向的对象。
- 在容器中保存指针是安全的
A. 无法进行左值复制赋值操作,但允许临时右值赋值构造和赋值
unique_ptr<string> p1(new string("I'm Li Ming!"));
unique_ptr<string> p2(new string("I'm age 22."));
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;
p1 = p2; // 禁止左值赋值
unique_ptr<string> p3(p2); // 禁止左值赋值构造
unique_ptr<string> p3(std::move(p1));
p1 = std::move(p2); // 使用move把左值转成右值就可以赋值了,效果和auto_ptr赋值一样
cout << "p1 = p2 赋值后:" << endl;
cout << "p1:" << p1.get() << endl;
cout << "p2:" << p2.get() << endl;
运行截图:
B. 在 STL 容器中使用unique_ptr,不允许直接赋值
vector<unique_ptr<string>> vec;
unique_ptr<string> p3(new string("I'm P3"));
unique_ptr<string> p4(new string("I'm P4"));
vec.push_back(std::move(p3));
vec.push_back(std::move(p4));
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;
vec[0] = vec[1]; /* 不允许直接赋值 */
vec[0] = std::move(vec[1]); // 需要使用move修饰,使得程序员知道后果
cout << "vec.at(0):" << *vec.at(0) << endl;
cout << "vec[1]:" << *vec[1] << endl;
当然,运行后是直接报错的,因为vec[1]已经是NULL了,再继续访问就越界了。
C. 支持对象数组的内存管理
// 会自动调用delete [] 函数去释放内存
unique_ptr<int[]> array(new int[5]); // 支持这样定义
除了上面ABC三项外,unique_ptr的其余用法都与auto_ptr用法一致。
-
构造
class Test { public: Test() { cout << "Test的构造函数..." << endl; } ~Test() { cout << "Test的析构函数..." << endl; } void doSomething() { cout << "do something......" << endl; } }; // 自定义一个内存释放其 class DestructTest { public: void operator()(Test *pt) { pt->doSomething(); delete pt; } }; // unique_ptr<T> up; 空的unique_ptr,可以指向类型为T的对象 unique_ptr<Test> t1; // unique_ptr<T> up1(new T()); 定义unique_ptr,同时指向类型为T的对象 unique_ptr<Test> t2(new Test); // unique_ptr<T[]> up; 空的unique_ptr,可以指向类型为T[的数组对象 unique_ptr<int[]> t3; // unique_ptr<T[]> up1(new T[]); 定义unique_ptr,同时指向类型为T的数组对象 unique_ptr<int[]> t4(new int[5]); // unique_ptr<T, D> up(); 空的unique_ptr,接受一个D类型的删除器D,使用D释放内存 unique_ptr<Test, DestructTest> t5; // unique_ptr<T, D> up(new T(
以上是关于C++ 智能指针 - 全部用法详解的主要内容,如果未能解决你的问题,请参考以下文章