C 的智能指针是一种特殊的指针类型,它能够自动管理内存资源,避免常见的内存泄漏和多次释放等问题。C 11引入了三种主要的智能指针:unique_ptr、shared_ptr和weak_ptr。
①unique_ptr
在C 中,unique_ptr是一个智能指针(smart pointer)类模板,用于管理动态分配的内存资源,它提供了自动释放内存的功能。与原始指针相比,unique_ptr有更高的安全性和易用性。
unique_ptr具有以下特点:
独占所有权:每个unique_ptr实例拥有对其所指向对象的唯一所有权。这意味着在任何时候只有一个unique_ptr可以指向一个特定的对象。
自动释放内存:当unique_ptr超出作用域或被重新赋值时,它所管理的内存会自动释放。这样就避免了内存泄漏的问题。
指针语义:unique_ptr的使用方式与原始指针相似,可以通过指针操作符(->)和解引用操作符(*)来访问所指向对象的成员。
不可拷贝:unique_ptr是不可拷贝的,即不能进行复制构造和赋值操作。这是为了确保独占所有权的特性,防止多个指针同时管理同一个对象的内存。
支持移动语义:unique_ptr支持移动构造和移动赋值操作,可以将所有权转移给新的unique_ptr,而无需进行内存拷贝。
可自定义删除器:unique_ptr可以通过模板参数来指定一个删除器(deleter)函数对象,用于在释放内存时执行额外的清理操作。
示例代码:
代码语言:javascript复制#include <memory>
int main() {
// 创建一个unique_ptr,指向一个动态分配的int对象
std::unique_ptr<int> ptr(new int(42));
// 使用指针操作符和解引用操作符访问所指向对象的值
std::cout << *ptr << std::endl; // 输出: 42
// 通过移动构造函数将所有权转移给另一个unique_ptr
std::unique_ptr<int> ptr2 = std::move(ptr);
// 注意,此时ptr已经为空指针,不再拥有对象的所有权
std::cout << *ptr2 << std::endl; // 输出: 42
// 使用自定义删除器
struct Deleter {
void operator()(int* p) {
std::cout << "Custom deleter called" << std::endl;
delete p;
}
};
std::unique_ptr<int, Deleter> ptr3(new int(100), Deleter());
// unique_ptr超出作用域时会自动释放内存,同时调用自定义删除器
return 0;
}
常见成员函数
operator*:解引用操作符,用于获取 unique_ptr 所指向对象的引用。
operator->
:箭头操作符,用于通过 unique_ptr 访问对象的成员函数或成员变量。
get:返回指向所管理对象的裸指针。
reset:重置 unique_ptr,释放当前所管理的对象并接管新的对象。
release:释放对所管理对象的控制权,并返回该指针的裸指针。
swap:交换两个 unique_ptr 的内容。
②shared_ptr
在C 中,shared_ptr是一个智能指针(smart pointer)类模板,用于管理动态分配的内存资源。与unique_ptr相比,shared_ptr可以实现多个指针共享同一块内存,并且提供了自动释放内存的功能。
shared_ptr具有以下特点:
共享所有权:多个shared_ptr实例可以同时指向同一个对象,它们共享对所指向对象的所有权。只有当所有shared_ptr都超出作用域或被重新赋值时,才会释放所管理的内存。
自动释放内存:当最后一个指向对象的shared_ptr超出作用域或被重新赋值时,它会自动释放所管理的内存。这种机制称为引用计数(reference counting),通过计数器来追踪当前有多少个shared_ptr指向同一块内存。
指针语义:shared_ptr的使用方式与原始指针相似,可以通过指针操作符(->)和解引用操作符(*)来访问所指向对象的成员。
可拷贝:shared_ptr是可拷贝的,即可以进行复制构造和赋值操作。每次拷贝会增加引用计数。当引用计数变为0时,表示没有任何shared_ptr指向该内存,会释放内存。
循环引用问题:如果存在循环引用(两个或多个对象相互持有shared_ptr),会导致内存泄漏。为了解决这个问题,可以使用弱引用指针weak_ptr。
示例代码:
代码语言:javascript复制#include <memory>
int main() {
// 创建一个shared_ptr,指向一个动态分配的int对象
std::shared_ptr<int> ptr1(new int(42));
// 使用指针操作符和解引用操作符访问所指向对象的值
std::cout << *ptr1 << std::endl; // 输出: 42
// 复制构造函数,共享同一块内存
std::shared_ptr<int> ptr2 = ptr1;
// 增加引用计数
std::cout << ptr1.use_count() << std::endl; // 输出: 2
// 通过弱引用指针weak_ptr解决循环引用问题
std::weak_ptr<int> weakPtr = ptr1;
// 使用lock()函数获取一个shared_ptr
std::shared_ptr<int> ptr3 = weakPtr.lock();
if (ptr3 != nullptr) {
// 成功获取shared_ptr
}
// 减少引用计数
ptr1.reset();
return 0;
}
常见成员函数
operator*:解引用操作符,用于获取 shared_ptr 所指向对象的引用。
operator->
:箭头操作符,用于通过 shared_ptr 访问对象的成员函数或成员变量。
get:返回指向所管理对象的裸指针。
reset:重置 shared_ptr,释放当前所管理的对象并接管新的对象。
release:释放对所管理对象的控制权,并返回该指针的裸指针。
swap:交换两个 shared_ptr 的内容。
use_count:返回当前被所有 shared_ptr 指向的对象的引用计数。
③weak_ptr
在 C 中,weak_ptr 是一种智能指针(smart pointer),用于解决循环引用问题。它是由 shared_ptr 派生而来,但不会增加引用计数,只是对所指向对象进行观察,并不拥有对象的所有权。
循环引用问题
循环引用问题指的是在使用shared_ptr管理对象时,存在两个或多个对象相互持有shared_ptr,形成一个循环引用的情况。这种情况下,每个对象的引用计数都不会变为0,导致内存泄漏。
具体来说,当两个对象相互持有shared_ptr时,它们的引用计数始终大于0,因此它们所指向的内存块永远不会被释放。即使程序使用结束,这部分内存也无法回收,造成了内存泄漏的问题。
循环引用问题的实际场景可能是两个对象之间存在双向关联,比如A对象持有shared_ptr指向B对象,而B对象也持有shared_ptr指向A对象。当这两个对象的生命周期延长,超过了程序实际需要它们的时间时,就会造成循环引用和内存泄露。
为了解决循环引用问题,C 中引入了弱引用指针weak_ptr。弱引用指针和shared_ptr不同,它不会增加引用计数,只是对所指向对象进行观察,并不拥有对象的所有权。通过弱引用指针,我们可以在需要时使用lock()函数获取一个有效的shared_ptr来操作对象,一旦对象的引用计数变为0,弱引用指针将自动失效。
使用弱引用指针可以破坏循环引用,让所有的shared_ptr都能够正常析构并释放所管理的内存,避免了潜在的内存泄漏风险。
weak_ptr 具有以下特点和用法:
弱引用:因为 weak_ptr 不会增加引用计数,所以当所有 shared_ptr 都释放后,weak_ptr 将自动失效。它允许你观察一个对象,但不影响其生命周期。
通过 shared_ptr 创建:通常,我们使用 shared_ptr 来初始化 weak_ptr。这样可以确保 weak_ptr 观察的对象仍然存在。
使用 lock() 获取 shared_ptr:要操作 weak_ptr 所观察的对象,可以使用 lock() 函数获取一个有效的 shared_ptr。如果原始的 shared_ptr 已经被释放,lock() 返回一个空的 shared_ptr。
判断是否有效:可以使用 expired() 函数来检查 weak_ptr 是否已经失效,即所观察的 shared_ptr 是否已经被释放。
解决循环引用问题:由于 weak_ptr 不增加引用计数,可以用于解决两个或多个对象之间的循环引用问题,避免内存泄漏。
示例代码:
代码语言:javascript复制#include <memory>
#include <iostream>
class B; // 前置声明
class A {
public:
std::shared_ptr<B> bPtr;
~A() { std::cout << "A destroyed" << std::endl; }
};
class B {
public:
std::weak_ptr<A> aWeakPtr;
~B() { std::cout << "B destroyed" << std::endl; }
};
int main() {
std::shared_ptr<A> aPtr = std::make_shared<A>();
std::shared_ptr<B> bPtr = std::make_shared<B>();
aPtr->bPtr = bPtr;
bPtr->aWeakPtr = aPtr;
std::cout << "Use count of aPtr: " << aPtr.use_count() << std::endl;
std::cout << "Use count of bPtr: " << bPtr.use_count() << std::endl;
// 使用 lock() 获取有效的 shared_ptr
std::shared_ptr<A> lockedAPtr = bPtr->aWeakPtr.lock();
if (lockedAPtr) {
// 使用 lockedAPtr 操作所观察的对象
std::cout << "A exists" << std::endl;
} else {
std::cout << "A does not exist" << std::endl;
}
return 0;
}
常见成员函数
expired:检查 weak_ptr 所观察的 shared_ptr 是否已经失效。
lock:获取一个有效的 shared_ptr,用于操作所观察的对象。如果原始的 shared_ptr 已经被释放,返回一个空的 shared_ptr。
use_count:返回当前被所有 shared_ptr 指向的对象的引用计数。
reset:重置 weak_ptr。