赞
踩
在软件开发与系统运维的广阔领域中,内存安全是基石亦是挑战。随着软件复杂度的增加,内存泄露、缓冲区溢出、越界访问等安全问题频发,不仅影响程序稳定性,更可能成为黑客攻击的入口。确保内存安全,意味着守护数据完整、预防系统崩溃,更是保障用户体验与业务连续性的关键。因此,深入探讨内存安全机制,采用先进防护技术,构建安全的内存使用环境,是每一位开发者与运维人员不可或缺的技能与责任。
C/C++语言可以方便的操作内存(通过指针等),因此能得到较高的运行效率。而指针是一把双刃剑,因为指针导致的问题也层出不穷,成为一个不可以绕过的问题。关于解决这个问题,C++也一直在努力,今天我们就来看看C++11中新增的智能指针。
C++中的智能指针其实是应用了“类对象出作用域后会自动调用其析构函数”的特点,将内存资源回收放在智能指针的析构函数中。以下是一个简单的自定义智能指针,旨在为了帮助大家理解其基本原理。
- // 简单的智能指针
- #include <iostream>
-
- template <typename T>
- class MyPointer{
- private:
- T* pointer_;
- public:
- MyPointer(T *p): pointer_(p){
- }
- ~MyPointer(){
- delete pointer_;
- std::cout << "pointer_ destroy.";
- }
- };
-
- int main(){
- MyPointer<int> p(new int(10));
- } // p对象会在这里析构,自动调用析构函数
std::unique_ptr 是 C++11 引入的一种智能指针,用于自动管理具有唯一所有权的动态分配的对象。所有的智能指针都在<memory>头文件中声明。
std::unique_ptr 保证其指向的对象在 unique_ptr 被销毁时自动被删除,从而避免了内存泄漏。与 std::shared_ptr 不同,std::unique_ptr 不可被复制(即,不能有两个 unique_ptr 指向同一个对象),但可以被移动(move),这允许 unique_ptr 的所有权在不同作用域之间转移。
基本用法
创建 std::unique_ptr
- #include <memory>
-
- int main() {
- // 使用 new 分配的内存初始化 unique_ptr
- std::unique_ptr<int> ptr(new int(10));
-
- // 使用 std::make_unique (C++14 及以后)
- auto ptr2 = std::make_unique<int>(20);
-
- return 0;
- }
访问 std::unique_ptr 指向的对象
- #include <iostream>
- #include <memory>
-
- int main() {
- std::unique_ptr<int> ptr(new int(10));
- std::cout << *ptr << std::endl; // 输出 10
- return 0;
- }
转移 std::unique_ptr 的所有权
- #include <iostream>
- #include <memory>
-
- void take_ownership(std::unique_ptr<int> ptr) {
- std::cout << *ptr << std::endl; // 原始 ptr 将不再拥有对象
- }
-
- int main() {
- std::unique_ptr<int> ptr(new int(10));
- take_ownership(std::move(ptr)); // 将所有权转移给函数
- // 此时 ptr 为空,访问 *ptr 将是未定义行为
- return 0;
- }
注意事项
std::shared_ptr 是 C++11 引入的另一种智能指针,用于自动管理具有共享所有权的动态分配的对象。与 std::unique_ptr 不同,std::shared_ptr 允许多个 shared_ptr 实例共享对同一对象的所有权。当最后一个 shared_ptr 被销毁或重置时,指向的对象才会被删除。这种机制通过内部使用一个控制块(通常是一个引用计数)来实现,该控制块跟踪有多少个 shared_ptr 实例指向某个对象。
基本用法
创建 std::shared_ptr
- #include <memory>
- #include <iostream>
-
- int main() {
- // 使用 new 分配的内存和 std::shared_ptr 构造函数
- std::shared_ptr<int> ptr1(new int(10));
-
- // 使用 std::make_shared(推荐方式)
- auto ptr2 = std::make_shared<int>(20);
-
- std::cout << *ptr1 << std::endl; // 输出 10
- std::cout << *ptr2 << std::endl; // 输出 20
-
- return 0;
- }
访问 std::shared_ptr 指向的对象
与 std::unique_ptr 类似,你可以通过解引用 shared_ptr 来访问其指向的对象。
std::cout << *ptr1 << std::endl; // 访问 ptr1 指向的对象
复制 std::shared_ptr
与 std::unique_ptr 不同,std::shared_ptr 支持复制操作,这会增加引用计数。
std::shared_ptr<int> ptr3 = ptr1; // 复制 ptr1,引用计数增加
转移 std::shared_ptr 的所有权(实际上是复制,因为所有权是共享的)
虽然 std::shared_ptr 可以通过移动语义进行转移,但在大多数情况下,这更像是复制操作,因为所有权是共享的。然而,你可以使用 std::move 来提高效率(尽管在 shared_ptr 的情况下,这种效率提升可能很小或不存在)。
auto ptr4 = std::move(ptr1); // 移动语义,但所有权仍然是共享的
注意事项
std::weak_ptr 是 C++11 引入的一种智能指针,用于解决 std::shared_ptr 可能导致的循环引用问题。std::weak_ptr 不拥有其所指向对象的所有权,即它不会增加对象的引用计数。相反,它提供了一种方式来访问一个由 std::shared_ptr 管理的对象,但不参与该对象的生命周期管理。
基本用法
创建 std::weak_ptr
std::weak_ptr 通常是通过一个 std::shared_ptr 来创建的,这样它就指向了由 std::shared_ptr 管理的同一个对象。
- #include <memory>
- #include <iostream>
-
- int main() {
- std::shared_ptr<int> sharedPtr = std::make_shared<int>(10);
- std::weak_ptr<int> weakPtr = sharedPtr; // weakPtr 现在指向 sharedPtr 管理的对象
-
- // 不能直接解引用 weakPtr,因为它不拥有对象
- // std::cout << *weakPtr << std::endl; // 这是错误的
-
- // 但可以通过 lock 方法尝试获取一个指向对象的 shared_ptr
- if (auto lockedPtr = weakPtr.lock()) {
- std::cout << *lockedPtr << std::endl; // 输出 10
- }
-
- return 0;
- }
使用 lock 方法
std::weak_ptr 的 lock 方法尝试返回一个指向其所指对象的 std::shared_ptr。如果原始 shared_ptr 仍然存在(即它指向的对象尚未被删除),则 lock 方法会成功并返回一个指向该对象的 shared_ptr。如果原始 shared_ptr 已被销毁(即它指向的对象已被删除),则 lock 方法会返回一个空的 shared_ptr。
注意事项
- #include <iostream>
- #include <memory>
-
- // 定义两个类,它们会相互引用对方
- class B; // 前向声明
-
- class A {
- public:
- std::shared_ptr<B> bPtr; // 使用shared_ptr来持有B的引用
- ~A() {
- std::cout << "A is being destroyed\n";
- }
- };
-
- class B {
- public:
- std::weak_ptr<A> aWeakPtr; // 使用weak_ptr来避免循环引用
- ~B() {
- std::cout << "B is being destroyed\n";
- }
- };
-
- int main() {
- // 创建两个shared_ptr,分别指向A和B的实例
- auto aPtr = std::make_shared<A>();
- auto bPtr = std::make_shared<B>();
-
- // 让A持有B的shared_ptr
- aPtr->bPtr = bPtr;
-
- // 让B持有A的weak_ptr,从而避免循环引用
- bPtr->aWeakPtr = aPtr;
-
- // 此时,没有直接的循环引用,因为B持有的是A的weak_ptr
-
- // 离开main函数的作用域时,aPtr和bPtr会被销毁
- // 由于bPtr持有的是aPtr的weak_ptr,它不会阻止aPtr的销毁
- // 当aPtr被销毁时,它会销毁其持有的bPtr(因为bPtr是一个shared_ptr)
- // 然后bPtr的销毁会触发B的析构函数,接着是A的析构函数(如果它还没有被销毁的话)
-
- return 0;
- }
- // 输出结果应该是:
- // B is being destroyed
- // A is being destroyed
- // 注意:输出的顺序可能因编译器和运行时环境的不同而有所变化
在这个示例中,A类持有一个指向B的std::shared_ptr,而B类持有一个指向A的std::weak_ptr。由于B不持有A的shared_ptr,因此不会增加A的引用计数,从而避免了循环引用。当main函数结束时,aPtr和bPtr(作为shared_ptr)会被销毁。由于bPtr持有aPtr的weak_ptr,它不会阻止aPtr的销毁。接着,aPtr的销毁会触发A的析构函数,并导致A持有的bPtr(A中的bPtr成员)也被销毁,进而触发B的析构函数。这样,两个对象都被正确地销毁了,没有发生内存泄漏。
智能指针是C++为了防止内存泄露的一种可行的手段,它解决了编程人员在内存分配和回收上的纠结。因此,在不极致追求效率的情况下(智能指针会增加一定的开销),建议使用智能指针。
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。