什么是智能指针?为什么需要这个东西?
这需要从C++最早的设计说起,C++为了提高语言的自由程度,允许使用C++的程序员自己控制内存,C++继承了C语言的指针特性,允许用户在C++中像C一样使用指针在堆中开辟一块较大的内存,但是由于性能的考虑,用户自己申请的内存需要用户自己主动释放。这就导致了有时遗忘释放时带来的内存泄漏问题。由此智能指针为了解决这个问题,诞生了。
C++的智能指针从C++11标准开始由boost引入, 关于boost的智能指针在此不再做介绍。
以下讨论的内容涉及了C++11-C++20中的技术规则。
在C++中使用智能指针需要包含<memory>头文件,智能指针作为STL的一部分来提供,所以在使用时需要使用std::命名空间,智能指针标准库提供了两个主要类:std::unique_ptr(私有指针)和std::shared_ptr(共享指针)。外加一个辅助类std::weak_ptr,下面一一介绍:
1.私有指针std::unique_ptr
std::unique_ptr是一个模板类,所以在使用的时候需要输入一个类型参数,并且标准库提供了一个内存申请函数用来替代new。std::make_unique.
1 #include<memory> 2 3 int main() { 4 5 std::unique_ptr<int> p = std::make_unique<int>(12); 6 7 // 类似于 int*p = new int(12); 8 return 0; 9 }
当私有智能智能指针超出当前作用域时,智能指针将释放自己持有的内存。也就是说,上面的p指针,在main函数返回时,释放所持有的内存。智能指针也可以搭配new来完成内存的申请。
1 #include<memory> 2 3 int main() { 4 5 6 std::unique_ptr<int> p2{ new int(13) }; 7 8 return 0; 9 }
此时,在p2超出作用域时,依然可以自动释放所持有的内存。
私有指针是不允许被复制的。
以下语法是错误的:
1 #include<memory> 2 3 int main() { 4 5 6 std::unique_ptr<int> p2{ new int(13) }; 7 8 std::unique_ptr<int> m = p2; // 错误,不允许被复制 9 10 return 0; 11 }
但是,可以被移动:
1 #include<memory> 2 3 int main() { 4 5 6 std::unique_ptr<int> p2{ new int(13) }; 7 8 std::unique_ptr<int> m = std::move(p2); 9 10 return 0; 11 }
私有指针要求,每一块内存只能有一个对象持有。所以符合移动复制的都是合法的,符合拷贝复制的都是不合法的。
对于智能指针数组,标准库依然有一套特化来解决。
1 #include<memory> 2 3 int main() { 4 5 6 std::unique_ptr<int[]> p = std::make_unique<int[]>(12); 7 8 return 0; 9 }
上面的代码中表示,申请了一块数组长度为12 int类型的连续内存。数组指针也可以配合new来完成,以达到数组的初始化工作。
1 #include<memory> 2 3 int main() { 4 5 6 std::unique_ptr<int[]> p{ new int[12]{12,23,45,67} }; 7 8 return 0; 9 }
2.共享指针std::shared_ptr
共享指针的使用方式和私有指针相似,但是共享指针是允许被复制的。
1 #include<memory> 2 3 int main() { 4 5 6 std::shared_ptr<int> p = std::make_shared<int>(12); 7 8 auto s = p; // ok 9 10 return 0; 11 }
共享指针的释放条件是:当所有持有该内存的指针对象全部消亡以后,释放该内存。
对于智能指针访问内部数据的方法是,该对象重载了*和->操作符。
1 #include<memory> 2 3 int main() { 4 5 6 std::shared_ptr<int> p = std::make_shared<int>(12); 7 8 int m = *p; // 读取p的内存值 9 10 int* s = p.get(); // 获取p的内存的地址 11 12 return 0; 13 }
私有智能指针允许将内存管理权限交于程序员自己管理。
1 #include<memory> 2 3 int main() { 4 5 6 auto p = std::make_unique<int>(12); 7 8 int* s = p.release(); // 此时智能指针对象在消亡时,不再主动释放内存。 9 10 return 0; 11 }
共享指针由于存在可能被多个对象持有的情况,所以不支持内存权限转移。
3. std::waek_ptr
1 #include<memory> 2 3 int main() { 4 5 6 auto p = std::make_shared<int>(12); 7 8 std::weak_ptr<int> wk(p); 9 if (!wk.lock()) { 10 // 对象已经释放。 11 } 12 wk.reset(); // 释放持有的智能指针对象。(注意:不是释放持有的智能指针的内存,而是释放持有的p对象的引用。) 13 14 return 0; 15 }
std::waek_ptr在使用时的主要用途是用来判断一个共享指针是否已经释放,对于智能指针的原理,每个人都有自己的一套见解,对于智能指针的原理,这就涉及到源码解析,由于篇幅过大,在此不再细说。
有几点是需要注意的:
1.尽量避免智能指针与普通指针的混用。(你要是想骚,那也没办法)。
2.避免重写智能指针的内存管理类。(同上)。
3.在团队项目上应与团队成员商议,是否采用智能指针。
智能指针的出现,是为了更方便,而不是为了更骚,更复杂,一切都应该建立在:此方法能够让我更舒服的且高效的工作。