MST

星途 面试题库

面试题:C++智能指针在防止重复释放内存中的应用

请详细阐述C++中`std::unique_ptr`、`std::shared_ptr`和`std::weak_ptr`在防止重复释放内存方面的原理及使用场景。若有一个复杂的类层次结构,其中基类指针需要在不同作用域中传递,如何合理使用智能指针确保内存不被重复释放且避免野指针?
19.7万 热度难度
编程语言C++

知识考点

AI 面试

面试题答案

一键面试

std::unique_ptr

  • 防止重复释放内存原理std::unique_ptr采用独占所有权模型。它持有对象的唯一所有权,当std::unique_ptr对象销毁(例如离开其作用域)时,它会自动释放所指向的内存。由于同一时刻只有一个std::unique_ptr可以指向给定对象,不存在其他指针可以同时释放该内存,从而防止了重复释放。
  • 使用场景:适用于当你希望明确某个对象的生命周期由单个所有者控制时。比如,函数内部创建的临时对象,其生命周期与函数调用紧密相关,在函数结束时应自动释放。另外,在管理动态分配的资源(如文件句柄、网络连接等)时,如果希望在对象销毁时自动关闭资源,std::unique_ptr也是很好的选择。

std::shared_ptr

  • 防止重复释放内存原理std::shared_ptr采用引用计数机制。多个std::shared_ptr可以指向同一个对象,每个std::shared_ptr维护一个指向对象的引用计数。每当创建一个新的std::shared_ptr指向该对象时,引用计数增加;每当一个std::shared_ptr销毁时,引用计数减少。当引用计数降为0时,表示没有任何std::shared_ptr再指向该对象,此时对象的内存会被自动释放。这样就保证了只有当所有指向对象的std::shared_ptr都不再使用时,才会释放内存,避免了重复释放。
  • 使用场景:适用于对象需要在多个地方被共享使用的情况,比如在一个复杂的系统中,多个模块可能需要访问同一个资源对象,且该资源对象的生命周期由所有使用者共同决定。例如,在实现一个缓存系统时,多个部分可能需要访问缓存中的数据,此时可以使用std::shared_ptr来管理缓存对象。

std::weak_ptr

  • 防止重复释放内存原理std::weak_ptr不增加对象的引用计数,它是一种弱引用,指向由std::shared_ptr管理的对象。它主要用于解决std::shared_ptr可能出现的循环引用问题(即两个或多个std::shared_ptr相互引用,导致引用计数永远不会降为0,内存无法释放)。std::weak_ptr可以观察std::shared_ptr所管理的对象,但不会影响其生命周期。当std::shared_ptr所管理的对象被释放后,std::weak_ptr会自动变为空指针,通过lock()方法可以检查对象是否还存在,如果对象存在则返回一个指向该对象的std::shared_ptr,否则返回空std::shared_ptr。这样既不会干扰std::shared_ptr的引用计数机制导致重复释放,又能在需要时安全地访问对象(若对象未被释放)。
  • 使用场景:常用于解决循环引用问题,比如在双向链表中,节点之间可能存在相互引用。如果使用std::shared_ptr来管理节点,很容易形成循环引用。此时可以使用std::weak_ptr来打破循环,例如在链表的一个方向上使用std::shared_ptr,而在另一个方向上使用std::weak_ptr。另外,当你需要观察一个对象但不想影响其生命周期时,也可以使用std::weak_ptr

复杂类层次结构中智能指针的使用

  • 使用std::shared_ptr传递基类指针:在复杂类层次结构中,若基类指针需要在不同作用域中传递,使用std::shared_ptr可以确保内存不被重复释放。因为多个std::shared_ptr可以指向同一个派生类对象(通过基类指针的多态性),它们共同维护对象的引用计数。只要还有一个std::shared_ptr存在,对象就不会被释放。例如:
class Base {
public:
    virtual ~Base() = default;
};

class Derived : public Base {
public:
    ~Derived() override {}
};

void someFunction(std::shared_ptr<Base> ptr) {
    // 在此处使用ptr,ptr的引用计数增加
}

int main() {
    std::shared_ptr<Base> basePtr = std::make_shared<Derived>();
    someFunction(basePtr);
    // 当basePtr离开main函数作用域时,若没有其他std::shared_ptr指向该对象,对象内存会被释放
    return 0;
}
  • 结合std::weak_ptr避免野指针:如果在不同作用域中可能存在对象生命周期结束后仍访问指针的情况,可以使用std::weak_ptr。例如,一个对象A持有指向另一个对象B的std::shared_ptr,而对象B需要反向引用对象A,但又不想影响A的生命周期。此时对象B可以持有指向A的std::weak_ptr。在需要访问A时,通过std::weak_ptrlock()方法检查A是否还存在,若存在则获取一个std::shared_ptr来安全地访问A,避免产生野指针。如下代码展示了这种场景:
class B;

class A {
public:
    std::shared_ptr<B> bPtr;
};

class B {
public:
    std::weak_ptr<A> aWeakPtr;
    void accessA() {
        auto aPtr = aWeakPtr.lock();
        if (aPtr) {
            // 安全访问A对象
        } else {
            // A对象已被释放
        }
    }
};

通过上述方式,使用std::shared_ptrstd::weak_ptr的组合,可以确保在复杂类层次结构中,内存不被重复释放且避免野指针的产生。