C++智能指针通过RAII机制自动化内存管理,避免内存泄漏和野指针。std::unique_ptr以独占所有权和移动语义确保资源唯一归属;std::shared_ptr通过引用计数实现共享所有权,但需警惕循环引用,可用std::weak_ptr打破;智能指针还可管理文件句柄、互斥锁等非内存资源;手动内存管理仅在底层编程、性能极致要求等少数场景下必要。

C++智能指针在内存管理中的核心作用,在于将资源(主要是堆内存)的生命周期管理自动化,通过RAII(Resource Acquisition Is Initialization)原则,有效避免了传统C++中常见的内存泄漏、野指针和二次释放等问题,极大地提升了代码的安全性、健壮性和开发效率。它让我们从繁琐的手动
new
delete
智能指针通过封装原始指针,并利用对象生命周期来管理其所指向的资源。当智能指针对象被销毁时(例如,超出作用域或被删除),它会自动释放所管理的资源。C++标准库提供了几种主要的智能指针类型,每种都有其特定的所有权语义和应用场景:
std::unique_ptr
std::shared_ptr
std::weak_ptr
std::unique_ptr
unique_ptr
unique_ptr
std::shared_ptr
shared_ptr
shared_ptr
shared_ptr
立即学习“C++免费学习笔记(深入)”;
std::weak_ptr
shared_ptr
weak_ptr
shared_ptr
weak_ptr
weak_ptr
shared_ptr
std::unique_ptr
我个人觉得,
unique_ptr
首先,
unique_ptr
delete
unique_ptr
std::unique_ptr<int> p1(new int(10)); std::unique_ptr<int> p2 = p1;
unique_ptr
其次,它拥抱了C++11引入的移动语义。虽然不能复制,但
unique_ptr
unique_ptr
unique_ptr
unique_ptr
unique_ptr
unique_ptr
std::move
std::unique_ptr<int> p2 = std::move(p1);
举个例子:
#include <iostream>
#include <memory>
#include <vector>
class MyObject {
public:
MyObject(int id) : id_(id) {
std::cout << "MyObject " << id_ << " constructed." << std::endl;
}
~MyObject() {
std::cout << "MyObject " << id_ << " destroyed." << std::endl;
}
void doSomething() {
std::cout << "MyObject " << id_ << " doing something." << std::endl;
}
private:
int id_;
};
// 工厂函数返回一个独占所有权的MyObject
std::unique_ptr<MyObject> createObject(int id) {
return std::make_unique<MyObject>(id); // 使用make_unique更安全高效
}
void processObject(std::unique_ptr<MyObject> obj) {
if (obj) { // 检查是否为空
obj->doSomething();
}
// obj 在这里超出作用域,它所拥有的MyObject会被自动销毁
}
int main() {
std::cout << "--- unique_ptr example start ---" << std::endl;
std::unique_ptr<MyObject> obj1 = createObject(1);
obj1->doSomething();
// 尝试复制会编译错误:
// std::unique_ptr<MyObject> obj_copy = obj1;
// 移动所有权
std::unique_ptr<MyObject> obj2 = std::move(obj1);
if (obj1) { // obj1 此时已经为空
obj1->doSomething(); // 不会执行
} else {
std::cout << "obj1 is now empty after move." << std::endl;
}
obj2->doSomething();
// 将所有权传递给函数
processObject(std::move(obj2));
if (!obj2) {
std::cout << "obj2 is now empty after passing to function." << std::endl;
}
// 在vector中存储unique_ptr
std::vector<std::unique_ptr<MyObject>> objects;
objects.push_back(std::make_unique<MyObject>(3));
objects.push_back(std::make_unique<MyObject>(4));
// 当objects超出作用域时,其中的MyObject也会被自动销毁
std::cout << "--- unique_ptr example end ---" << std::endl;
return 0;
}输出会清晰地展示对象的构造和销毁时机,以及所有权转移后的状态。
unique_ptr
std::shared_ptr
初次接触
shared_ptr
shared_ptr
潜在陷阱:循环引用
当两个或多个
shared_ptr
shared_ptr
shared_ptr
考虑一个简单的双向链表节点:
#include <iostream>
#include <memory>
class Node {
public:
int value;
std::shared_ptr<Node> next;
std::shared_ptr<Node> prev; // 这里的prev是问题所在
Node(int val) : value(val) {
std::cout << "Node " << value << " constructed." << std::endl;
}
~Node() {
std::cout << "Node " << value << " destroyed." << std::endl;
}
};
void createCircularReference() {
std::shared_ptr<Node> node1 = std::make_shared<Node>(1);
std::shared_ptr<Node> node2 = std::make_shared<Node>(2);
node1->next = node2; // node2的引用计数变为2
node2->prev = node1; // node1的引用计数变为2
// 此时,即使node1和node2超出作用域
// node1的引用计数仍为1(被node2->prev引用)
// node2的引用计数仍为1(被node1->next引用)
// 它们都不会被销毁,内存泄漏
} // node1, node2超出作用域,但Node 1和Node 2的析构函数不会被调用
int main() {
std::cout << "--- Shared_ptr circular reference example start ---" << std::endl;
createCircularReference();
std::cout << "--- Shared_ptr circular reference example end ---" << std::endl;
// 你会发现,程序结束时,Node 1和Node 2的析构函数并没有被调用
return 0;
}最佳实践:使用 std::weak_ptr
std::weak_ptr
shared_ptr
weak_ptr
shared_ptr
shared_ptr
修改上面的链表节点,将
prev
weak_ptr
#include <iostream>
#include <memory>
class NodeFixed {
public:
int value;
std::shared_ptr<NodeFixed> next;
std::weak_ptr<NodeFixed> prev; // 使用weak_ptr
NodeFixed(int val) : value(val) {
std::cout << "NodeFixed " << value << " constructed." << std::endl;
}
~NodeFixed() {
std::cout << "NodeFixed " << value << " destroyed." << std::endl;
}
};
void createNoCircularReference() {
std::shared_ptr<NodeFixed> node1 = std::make_shared<NodeFixed>(1);
std::shared_ptr<NodeFixed> node2 = std::make_shared<NodeFixed>(2);
node1->next = node2; // node2的引用计数变为2
node2->prev = node1; // node1的引用计数不变,因为是weak_ptr
// 此时,当node1和node2超出作用域时
// node1的引用计数降为0,NodeFixed 1被销毁
// NodeFixed 1销毁后,node1->next(即node2)的引用计数降为1
// 接着node2的引用计数降为0,NodeFixed 2被销毁
} // node1, node2超出作用域,NodeFixed 1和NodeFixed 2的析构函数会被调用
int main() {
std::cout << "--- Shared_ptr with weak_ptr example start ---" << std::endl;
createNoCircularReference();
std::cout << "--- Shared_ptr with weak_ptr example end ---" << std::endl;
// 此时,程序结束时,你会看到NodeFixed 1和NodeFixed 2的析构函数都被调用了
return 0;
}其他最佳实践:
shared_ptr
std::shared_ptr<T> p1(raw_ptr); std::shared_ptr<T> p2(raw_ptr);
shared_ptr
shared_ptr
std::enable_shared_from_this
std::make_shared
new
shared_ptr
make_shared
shared_ptr
shared_ptr
shared_ptr
std::mutex
别以为智能指针只管内存那点事,它的“野心”可大着呢。智能指针,尤其是
std::unique_ptr
unique_ptr
一些常见的非内存资源管理场景包括:
文件句柄: 在C语言风格的API中,文件通常通过
FILE*
fclose()
#include <iostream>
#include <memory>
#include <cstdio> // For FILE, fopen, fclose
// 自定义删除器,用于关闭文件
struct FileCloser {
void operator()(FILE* file) const {
if (file) {
std::cout << "Closing file..." << std::endl;
fclose(file);
}
}
};
int main() {
// 使用unique_ptr管理FILE*,并指定自定义删除器
std::unique_ptr<FILE, FileCloser> filePtr(fopen("example.txt", "w"));
if (filePtr) {
fprintf(filePtr.get(), "Hello from unique_ptr!\n");
std::cout << "File written successfully." << std::endl;
} else {
std::cerr << "Failed to open file." << std::endl;
}
// filePtr超出作用域时,FileCloser会被调用,自动关闭文件
return 0;
}互斥锁(Mutex): 在多线程编程中,
std::lock_guard
std::unique_lock
unique_ptr
#include <iostream>
#include <memory>
#include <mutex> // For std::mutex
std::mutex myMutex;
// 自定义删除器,用于解锁互斥量
struct MutexUnlocker {
void operator()(std::mutex* mtx) const {
if (mtx && mtx->try_lock()) { // 避免解锁未锁定的互斥量
mtx->unlock();
std::cout << "Mutex unlocked." << std::endl;
} else if (mtx) {
std::cout << "Mutex was already unlocked or could not be locked for unlock." << std::endl;
}
}
};
void accessProtectedResource() {
// 尝试锁定互斥量,并用unique_ptr管理其生命周期
std::unique_ptr<std::mutex, MutexUnlocker> lockGuard(&myMutex);
myMutex.lock(); // 实际锁定
std::cout << "Resource accessed, mutex is locked." << std::endl;
// lockGuard超出作用域时,myMutex会被自动解锁
} // 离开作用域时,MutexUnlocker被调用,解锁myMutex当然,对于标准库的
std::mutex
std::lock_guard
std::unique_lock
unique_ptr
网络套接字、数据库连接、图形API资源(如OpenGL纹理、DirectX缓冲区): 这些资源通常需要显式的创建和销毁函数。通过自定义删除器,
unique_ptr
unique_ptr
closesocket()
动态链接库句柄: 在Windows上是
HMODULE
FreeLibrary()
void*
dlclose()
unique_ptr
关键在于,
unique_ptr
Deleter
unique_ptr
虽然智能指针大大简化了开发,但我有时还是会遇到那些不得不亲自动手“搬砖”的场景。在现代C++中,手动管理内存(即直接使用
new
delete
底层系统编程和嵌入式开发: 在资源极其有限的嵌入式系统、操作系统内核或设备驱动程序中,对内存的精确控制至关重要。智能指针的额外开销(即使很小,如
shared_ptr
new
delete
自定义内存分配器: 当你需要实现自己的内存池、竞技场分配器(arena allocator)或其他高性能、特定用途的内存管理策略时,你通常会直接操作原始内存块,而不是通过智能指针。智能指针可以用来管理这些自定义分配器分配出来的对象,但分配器本身的操作往往是手动的。
与C语言API交互: 很多C库返回的是原始指针,并且要求你手动调用特定的
free
malloc
free
SDL_malloc
SDL_free
unique_ptr
性能极度敏感的代码: 在某些对性能有极致要求的场景下,即使是
unique_ptr
实现智能指针本身: 显然,当你自己在实现一个智能指针类时,你需要在其内部直接使用
new
delete
某些数据结构和算法: 在实现一些复杂的数据结构(例如,某些形式的图、树或自定义容器)时,为了优化内存布局、减少碎片或实现特殊的内存管理策略,可能需要直接控制对象的创建和销毁。
即便在这些场景下,我也倾向于将手动内存管理的代码封装在尽可能小的模块中,并确保其边界清晰、经过严格测试。总的来说,智能指针是现代C++的基石,它让我们的代码更安全、更可靠。手动内存管理是最后的手段,只在确实没有其他更好选择时才考虑。
以上就是C++智能指针在内存管理中的应用的详细内容,更多请关注php中文网其它相关文章!
每个人都需要一台速度更快、更稳定的 PC。随着时间的推移,垃圾文件、旧注册表数据和不必要的后台进程会占用资源并降低性能。幸运的是,许多工具可以让 Windows 保持平稳运行。
Copyright 2014-2025 https://www.php.cn/ All Rights Reserved | php.cn | 湘ICP备2023035733号