一、什么是智能指针
C++ 中的智能指针是一种特殊类型的指针,它们能够自动管理对象的生命周期,通过封装原始指针并提供额外的功能来确保资源在不再需要时能够被正确地释放,帮助程序员避免常见的内存管理问题,如内存泄漏和悬挂指针(dangling pointers)。
二、智能指针的分类
1、std::unique_ptr
std::unique_ptr 是一个独占所有权的智能指针。在任何给定的时间,一个 unique_ptr 可以指向一个对象,并且该对象只能被一个 unique_ptr 所拥有。当 unique_ptr 被销毁(例如,超出其作用域)时,它所指向的对象也会被自动删除。
示例:
#include <memory>
int main() {
std::unique_ptr<int> ptr(new int(5));
// ... 使用 ptr
// 当 ptr 离开作用域时,它所指向的 int 对象会被自动删除
return 0;
}
2、std::shared_ptr
std::shared_ptr 是一个共享所有权的智能指针。多个 shared_ptr 可以指向同一个对象,并且当最后一个指向该对象的 shared_ptr 被销毁时,该对象才会被删除。这通过引用计数来实现。
示例:
#include <memory>
int main() {
std::shared_ptr<int> ptr1 = std::make_shared<int>(5);
std::shared_ptr<int> ptr2 = ptr1; // ptr1 和 ptr2 现在共享同一个 int 对象
// ... 使用 ptr1 和 ptr2
// 当 ptr1 和 ptr2 都离开作用域时,它们所指向的 int 对象才会被删除
return 0;
}
3、std::weak_ptr
std::weak_ptr 是一个弱引用智能指针,它指向一个由 std::shared_ptr 管理的对象。与 std::shared_ptr 不同,std::weak_ptr 不会增加引用计数。它主要用于解决 std::shared_ptr 之间的循环引用问题。
下面是使用std::shared_ptr导致循环引用,以及如何使用std::weak_ptr来解决的例子:
#include <iostream>
#include <memory>
class Bar;
class Foo {
public:
std::shared_ptr<Bar> barPtr; // 假设Foo持有一个Bar的shared_ptr
// ... 其他成员和方法 ...
~Foo() {
std::cout << "Foo is being destroyed\n";
}
};
class Bar {
public:
std::shared_ptr<Foo> fooPtr; // 假设Bar持有一个Foo的shared_ptr
// ... 其他成员和方法 ...
~Bar() {
std::cout << "Bar is being destroyed\n";
}
};
int main() {
// 创建一个Foo和Bar的循环引用
std::shared_ptr<Foo> foo = std::make_shared<Foo>();
std::shared_ptr<Bar> bar = std::make_shared<Bar>();
foo->barPtr = bar;
bar->fooPtr = foo;
// 此时foo和bar的引用计数都是1,因为彼此相互引用
// 当main函数结束时,foo和bar会离开作用域,但它们的引用计数仍然是1,因此它们都不会被删除
// 为了避免内存泄漏,我们需要打破循环引用
// 使用weak_ptr来修改Bar类
class BarWithWeak {
public:
std::weak_ptr<Foo> fooPtr; // 使用weak_ptr代替shared_ptr
// ... 其他成员和方法 ...
~BarWithWeak() {
std::cout << "BarWithWeak is being destroyed\n";
}
};
// 重新创建对象,但这次使用BarWithWeak
std::shared_ptr<Foo> foo2 = std::make_shared<Foo>();
std::shared_ptr<BarWithWeak> bar2 = std::make_shared<BarWithWeak>();
foo2->barPtr = bar2; // foo2仍然持有bar2的shared_ptr
bar2->fooPtr = foo2; // 但bar2持有foo2的weak_ptr
//foo2对象由bar2这个std::shared_ptr持有,引用计数为1
//bar2对象由foo2这个std::shared_ptr持有,引用计数为1
//设置foo2->barPtr = bar2;和bar2->fooPtr = foo2后,虽然形成了循环引用,但fooPtr是std::weak_ptr,不会增加foo2的引用计数。
//当foo2和bar2离开作用域时:foo2的引用计数变为0,触发foo2的析构函数。在析构函数中,由于fooPtr是std::weak_ptr,它不会阻止foo2的析构。
//随后,bar2的引用计数也变为0(因为bar2这个std::shared_ptr被销毁),触发bar2的析构函数。
return 0;
}
1、使用std::weak_ptr可以打破循环引用,允许对象在它们的最后一个std::shared_ptr被销毁时正确析构。
2、std::weak_ptr不持有对象的所有权,因此不会增加引用计数。它只是提供了一种在不知道对象是否仍然存在的情况下安全地访问该对象的方式。
3、可使用std::weak_ptr::lock()用于尝试从 std::weak_ptr 获取一个 std::shared_ptr,如果原始对象(即 weak_ptr 所指向的对象)仍然存在(即尚未被所有 std::shared_ptr 销毁),则 lock() 函数会返回一个指向该对象的 std::shared_ptr。如果原始对象已经不存在,则 lock() 将返回一个空的 std::shared_ptr。
4、当所有std::shared_ptr都销毁后,对象的引用计数会变为0,从而触发其析构函数。
4、std::auto_ptr(已弃用)
std::auto_ptr是C++98中引入的一种智能指针,但在C++11中已被弃用,并在C++17中被移除。它的主要问题是所有权转移语义,当auto_ptr被复制时,它会将其所有权转移到新的auto_ptr,这可能导致意外的行为。因此,建议使用unique_ptr或shared_ptr代替。
三、总结
智能指针可以简单理解为不需要手动释放内存的指针。
示例:
// 在堆上分配一个整数
int* pInt = new int(10);
std::cout << "Value of pInt: " << *pInt << std::endl;
// 在堆上分配一个MyClass对象
MyClass* pObj = new MyClass(20);
std::cout << "Value of pObj->value: " << pObj->value << std::endl;
// 使用完后,必须手动释放内存
delete pInt;
delete pObj;
// 使用std::unique_ptr在堆上分配一个整数(但通常不这样做,因为unique_ptr主要用于复杂类型)
std::unique_ptr<int> pInt(new int(10));
std::cout << "Value of pInt: " << *pInt << std::endl;
// 使用std::unique_ptr在堆上分配一个MyClass对象
std::unique_ptr<MyClass> pObj(new MyClass(20));
std::cout << "Value of pObj->value: " << pObj->value << std::endl;
// 无需手动调用delete,因为std::unique_ptr会在离开作用域时自动释放内存
return 0;