C++ 智能指针的使用原因及场景分析
在现代 C++ 开发中,资源管理(包括内存、文件句柄、锁等)是一个至关重要的问题。特别是在异常安全性设计中,如何避免资源泄漏是开发者必须面对的挑战。
详细讲解了 C++ 智能指针的使用原因、场景分析及 RAII 设计思想。内容涵盖 std::unique_ptr、std::shared_ptr 和 std::weak_ptr 的原理与实现,重点分析了循环引用问题及其解决方案。此外,文章还探讨了智能指针的线程安全问题、内存泄漏的危害与检测方法,并通过代码示例展示了如何避免资源泄漏,提升代码的异常安全性和可维护性。

在现代 C++ 开发中,资源管理(包括内存、文件句柄、锁等)是一个至关重要的问题。特别是在异常安全性设计中,如何避免资源泄漏是开发者必须面对的挑战。
在 C++ 中,资源的申请与释放通常是手动管理的。比如 new 分配内存,delete 释放内存。但是,以下场景会导致资源管理复杂化:
当程序执行到一半抛出异常时,如果之前分配的资源没有及时释放,就会产生资源泄漏问题。例如:
doubleDivide(int a, int b) {
if (b == 0) {
throw "Divide by zero condition!"; // 抛出异常
}
return static_cast<double>(a) / b;
}
void Func() {
int* array1 = new int[10]; // 申请资源 1
int* array2 = new int[10]; // 申请资源 2
try {
int len, time; std::cin >> len >> time; std::cout << Divide(len, time) << std::endl; // 可能抛出异常
} catch (...) {
std::cout << "delete [] array1" << std::endl;
delete[] array1; // 释放资源 1
std::cout << "delete [] array2" << std::endl;
delete[] array2; // 释放资源 2
throw; // 重新抛出异常
}
// 手动释放资源
delete[] array1;
delete[] array2;
}
上面的代码逻辑看似解决了资源释放问题,但:
new array2 本身抛出异常,array1 也会因为没有被释放而造成资源泄漏。因此,为了解决这些问题,C++ 引入了 RAII(资源获取即初始化)思想,结合智能指针自动管理资源。
RAII(Resource Acquisition Is Initialization) 是一种设计原则:资源的申请和释放绑定到对象的生命周期中。通过智能指针,将资源管理从手动控制转变为自动化管理。
C++11 标准引入了三种常用智能指针:
智能指针包含在头文件
<memory>中
std::unique_ptr:独占式所有权,适用于单个对象。std::shared_ptr:共享式所有权,适用于多个对象共享。std::weak_ptr:弱引用,解决 shared_ptr 循环引用问题。以下是使用智能指针优化上面代码的实现。
#include <iostream>
#include <memory>
#include <stdexcept>
doubleDivide(int a, int b) {
if (b == 0) {
throw std::runtime_error("Divide by zero condition!");
}
return static_cast<double>(a) / b;
}
void Func() {
// 使用智能指针管理动态数组
std::unique_ptr<int[]> array1(new int[10]);
std::unique_ptr<int[]> array2(new int[10]);
try {
int len, time; std::cin >> len >> time; std::cout << Divide(len, time) << std::endl;
} catch (...) {
// 不需要手动 delete,智能指针会自动释放资源
throw; // 重新抛出异常
}
}
int main() {
try {
Func();
} catch (const std::exception& e) {
std::cout << e.what() << std::endl;
} catch (...) {
std::cout << "未知异常" << std::endl;
}
return 0;
}
delete,降低了手动管理资源的复杂度。另一个常见问题是析构函数中抛出异常。例如:
class Resource {
public:
~Resource() {
// 假设要释放多个资源
for (int i = 0; i < 10; ++i) {
if (i == 5) // 假设释放到一半时出错
throw std::runtime_error("Error during destruction");
}
}
};
如果析构函数抛出异常,会导致程序在堆栈展开过程中不知如何处理,可能直接导致程序崩溃。
在 C++ 开发中,资源管理(如内存、文件、网络连接等)是一个常见且关键的问题。如果资源没有被正确释放,可能会导致资源泄漏,进而引发性能问题甚至程序崩溃。为了高效且安全地管理资源,C++ 引入了RAII(资源获取即初始化)设计思想,而智能指针则是 RAII 思想的一种具体实现。
RAII 是 Resource Acquisition Is Initialization 的缩写,中文翻译为'资源获取即初始化'。其核心思想是将资源的管理与对象的生命周期绑定,通过对象的构造函数获取资源,并在析构函数中释放资源。RAII 的优点包括:
RAII 将资源的获取与对象的初始化绑定。例如:
RAII 管理的资源可以包括:
new/delete)智能指针是 RAII 的一个典型实现,它不仅符合 RAII 的设计理念,还通过重载运算符模拟指针的行为,使资源的访问更加方便。例如:
operator* 和 operator->,可以像普通指针一样操作资源。operator[],可以支持访问数组元素的操作。以下是一个简单的智能指针类的实现:
template<class T>
class SmartPtr {
public:
// 构造函数:获取资源
SmartPtr(T* ptr) : _ptr(ptr) {}
// 析构函数:释放资源
~SmartPtr() {
std::cout << "delete[] " << _ptr << std::endl;
delete[] _ptr; // 确保资源释放
}
// 重载运算符,方便访问资源
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
T& operator[](size_t i) { return _ptr[i]; }
private:
T* _ptr; // 内部指针,管理资源
};
下面的示例展示了如何使用自定义的智能指针类解决资源管理问题。
#include <iostream>
using namespace std;
template<class T>
class SmartPtr {
public:
SmartPtr(T* ptr) : _ptr(ptr) {}
~SmartPtr() { cout << "delete[] " << _ptr << endl; delete[] _ptr; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
T& operator[](size_t i) { return _ptr[i]; }
private:
T* _ptr;
};
doubleDivide(int a, int b) {
if (b == 0) throw "Divide by zero condition!";
return static_cast<double>(a) / b;
}
void Func() {
// 使用智能指针管理动态数组
SmartPtr<int> sp1 = new int[10];
SmartPtr<int> sp2 = new int[10];
for (size_t i = 0; i < 10; i++) {
sp1[i] = sp2[i] = i;
}
int len, time; cin >> len >> time; cout << Divide(len, time) << endl;
}
int main() {
try {
Func();
} catch (const char* errmsg) { cout << errmsg << endl; }
catch (const exception& e) { cout << e.what() << endl; }
catch (...) { cout << "未知异常" << endl; }
return 0;
}
SmartPtr 构造时获取资源(new int[10]),析构时释放资源(delete[])。Divide 函数中抛出异常,SmartPtr 对象会在函数退出时调用析构函数,自动释放资源。delete,释放逻辑被封装到智能指针中。sp1[i])、指针操作(*sp1)等,使用体验接近普通指针。| 特性 | 原生指针 | 智能指针 |
|---|---|---|
| 资源释放 | 手动调用 delete | 析构函数自动释放 |
| 异常安全性 | 容易造成资源泄漏 | 保证异常安全 |
| 使用复杂度 | 需要手动管理资源 | 自动化管理,降低复杂度 |
| 指针行为支持 | 支持基本指针操作 | 重载运算符,模拟指针行为 |
循环引用问题(shared_ptr) | 存在 | 通过 weak_ptr 解决 |
C++ 标准库中的智能指针提供了一种安全、高效的资源管理方式,减少了资源泄漏和悬空指针的风险,同时显著提高了代码的异常安全性和可读性。以下是 C++ 标准库智能指针的全面介绍及使用示例。
智能指针主要有以下几种类型,均定义在 <memory> 头文件中:
std::auto_ptr (C++98,已废弃):
std::unique_ptr(C++11 引入):
std::shared_ptr(C++11 引入):
std::weak_ptr(C++11 引入):
shared_ptr 循环引用问题。std::unique_ptrstd::unique_ptr 是一个独占式智能指针,不能被拷贝,但可以通过移动语义转移资源管理权。
#include <iostream>
#include <memory>
struct Date {
int _year, _month, _day;
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
~Date() { std::cout << "~Date()" << std::endl; }
};
int main() {
std::unique_ptr<Date> up1(new Date(2024, 11, 16));
// 不支持拷贝
// std::unique_ptr<Date> up2 = up1; // 编译错误
// 支持移动
std::unique_ptr<Date> up3 = std::move(up1);
if (!up1) {
std::cout << "up1 is null after move." << std::endl;
}
if (up3) {
std::cout << "up3 owns the resource." << std::endl;
}
return 0;
}
特性:
delete。std::shared_ptrstd::shared_ptr 是一个共享式智能指针,底层通过引用计数控制资源生命周期。
#include <iostream>
#include <memory>
struct Date {
int _year, _month, _day;
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
~Date() { std::cout << "~Date()" << std::endl; }
};
int main() {
std::shared_ptr<Date> sp1(new Date(2024, 11, 16));
std::shared_ptr<Date> sp2 = sp1; // 拷贝,引用计数增加
std::cout << "sp1 use_count: " << sp1.use_count() << std::endl;
sp2->_year = 2025;
std::cout << "Year: " << sp1->_year << std::endl; // sp1 和 sp2 共享资源
return 0;
}
特性:
shared_ptr 被销毁时,资源才会被释放。std::weak_ptrstd::weak_ptr 是一种非 RAII 的弱引用智能指针,设计用于解决 shared_ptr 的循环引用问题。
#include <iostream>
#include <memory>
struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 弱引用,防止循环引用
~Node() { std::cout << "~Node()" << std::endl; }
};
int main() {
auto node1 = std::make_shared<Node>();
auto node2 = std::make_shared<Node>();
node1->next = node2;
node2->prev = node1; // 弱引用避免循环引用
return 0;
}
特性:
shared_ptr 管理的资源。lock() 方法获取 shared_ptr。默认情况下,智能指针使用 delete 或 delete[] 释放资源。如果资源不是通过 new 分配的,可以通过自定义删除器指定释放方式。
#include <iostream>
#include <memory>
#include <cstdio>
void fileCloser(FILE* file) {
std::cout << "Closing file." << std::endl;
fclose(file);
}
int main() {
std::shared_ptr<FILE> file(fopen("example.txt", "w"), fileCloser);
if (file) {
std::cout << "File opened successfully." << std::endl;
}
return 0;
}
make_shared 与资源初始化使用 make_shared 可以直接构造 shared_ptr 对象,性能更高,异常安全性更强。
#include <iostream>
#include <memory>
struct Date {
int _year, _month, _day;
Date(int year, int month, int day) : _year(year), _month(month), _day(day) {}
~Date() { std::cout << "~Date()" << std::endl; }
};
int main() {
auto sp = std::make_shared<Date>(2024, 11, 16);
std::cout << "Year: " << sp->_year << std::endl;
return 0;
}
优势:
new 分配的资源。std::weak_ptr 打破循环引用。explicit 的,防止隐式类型转换。| 类型 | RAII | 拷贝支持 | 移动支持 | 适用场景 |
|---|---|---|---|---|
std::auto_ptr | 是 | 是 | 否 | 已废弃,不建议使用 |
std::unique_ptr | 是 | 否 | 是 | 独占资源管理 |
std::shared_ptr | 是 | 是 | 是 | 共享资源管理 |
std::weak_ptr | 否 | 否 | 否 | 配合 shared_ptr 防止循环引用 |
智能指针是 C++ 提供的一种封装原生指针的类,其核心原理是通过 RAII(资源获取即初始化)设计模式,将资源的管理与智能指针对象的生命周期绑定,从而实现资源的自动管理和释放,避免资源泄漏、悬空指针等问题。
RAII 是智能指针的核心设计思想,资源的获取和释放分别绑定到智能指针对象的构造函数和析构函数中:
RAII 确保了在异常抛出或正常退出作用域时,智能指针的析构函数能够被自动调用,从而释放资源,避免资源泄漏。
智能指针通过内部成员变量封装原生指针(如 T* _ptr),并通过重载运算符(*、->)实现类似原生指针的操作行为。例如:
operator*:访问资源本体。operator->:访问资源的成员。template<class T>
class SmartPtr {
private:
T* _ptr; // 封装原生指针
public:
SmartPtr(T* ptr) : _ptr(ptr) {} // 资源获取
~SmartPtr() { delete _ptr; } // 资源释放
T& operator*() { return *_ptr; } // 解引用
T* operator->() { return _ptr; } // 指针访问
};
不同类型的智能指针采用不同的资源管理方式:
unique_ptrunique_ptr 对象管理。shared_ptrshared_ptr 对象可以共享同一资源。class SharedPtr {
private:
T* _ptr; // 封装原生指针
int* _ref_count; // 引用计数
public:
SharedPtr(T* ptr) : _ptr(ptr), _ref_count(new int(1)) {}
SharedPtr(const SharedPtr& other) : _ptr(other._ptr), _ref_count(other._ref_count) {
++(*_ref_count); // 引用计数增加
}
~SharedPtr() {
if (--(*_ref_count) == 0) { // 引用计数为 0 时释放资源
delete _ptr;
delete _ref_count;
}
}
};
weak_ptrweak_ptr 不直接管理资源,而是观察由 shared_ptr 管理的资源。智能指针的一个核心特点是:在智能指针对象的生命周期结束时,其析构函数会被自动调用,确保资源的正确释放。这种自动调用依赖于以下机制:
shared_ptr)shared_ptr 的引用计数需要动态分配:
shared_ptr,分配一份资源和一个引用计数。shared_ptr 共享一个引用计数。shared_ptr 被拷贝时,引用计数增加。shared_ptr 被销毁时,引用计数减少。当两个或多个 shared_ptr 互相引用时,会导致引用计数永不为 0,从而资源无法释放。这时需要 weak_ptr 来打破循环。
智能指针允许通过删除器(deleter)定制资源释放方式。例如:
delete 或 delete[]。fclose)、内存池释放等。std::shared_ptr<FILE> file(fopen("test.txt", "r"), [](FILE* f) { fclose(f); });
shared_ptr):动态分配引用计数,管理资源生命周期。weak_ptr 解决 shared_ptr 的循环引用问题。智能指针本质是通过封装原生指针和引用计数,实现安全、高效的资源管理,同时提升了代码的可维护性和异常安全性。
auto_ptrtemplate<class T>
class auto_ptr {
public:
auto_ptr(T* ptr) : _ptr(ptr) {}
auto_ptr(auto_ptr<T>& sp) : _ptr(sp._ptr) { sp._ptr = nullptr; }
auto_ptr<T>& operator=(auto_ptr<T>& ap) {
if (_ptr != ap._ptr) {
if (_ptr) delete _ptr;
_ptr = ap._ptr;
ap._ptr = NULL;
}
return *this;
}
~auto_ptr() {
if (_ptr) { cout << "delete:" << _ptr << endl; delete _ptr; }
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
};
unique_ptrtemplate<class T>
class unique_ptr {
public:
explicit unique_ptr(T* ptr) : _ptr(ptr) {}
~unique_ptr() {
if (_ptr) { cout << "delete:" << _ptr << endl; delete _ptr; }
}
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
unique_ptr(const unique_ptr<T>& sp) = delete;
unique_ptr<T>& operator=(const unique_ptr<T>& sp) = delete;
unique_ptr(unique_ptr<T>&& sp) : _ptr(sp._ptr) { sp._ptr = nullptr; }
unique_ptr<T>& operator=(unique_ptr<T>&& sp) {
delete _ptr; _ptr = sp._ptr; sp._ptr = nullptr;
return *this;
}
private:
T* _ptr;
};
shared_ptrtemplate<class T>
class shared_ptr {
public:
explicit shared_ptr(T* ptr = nullptr) : _ptr(ptr), _pcount(new int(1)) {}
template<class D>
shared_ptr(T* ptr, D del) : _ptr(ptr), _pcount(new int(1)), _del(del) {}
shared_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del) {
++(*_pcount);
}
shared_ptr<T>& operator=(const shared_ptr<T>& sp) {
if (_ptr != sp._ptr) {
if (--(*_pcount) == 0) {
_del(_ptr);
delete _pcount;
_ptr = nullptr;
_pcount = nullptr;
}
_ptr = sp._ptr;
_pcount = sp._pcount;
++(*_pcount);
_del = sp._del;
}
return *this;
}
~shared_ptr() {
if (--(*_pcount) == 0) {
_del(_ptr);
_ptr = nullptr;
delete _pcount;
_pcount = nullptr;
}
}
T* get() const { return _ptr; }
int use_count() const { return *_pcount; }
T& operator*() { return *_ptr; }
T* operator->() { return _ptr; }
private:
T* _ptr;
int* _pcount;
function<void(T*)> _del = [](T* ptr) { delete ptr; };
};
weak_ptrtemplate<class T>
class weak_ptr {
public:
weak_ptr() {}
weak_ptr(const shared_ptr<T>& sp) : _ptr(sp.get()) {}
weak_ptr<T>& operator=(const shared_ptr<T>& sp) {
_ptr = sp.get();
return *this;
}
private:
T* _ptr = nullptr;
};
shared_ptr 循环引用引出 weak_ptr 的作用在使用 shared_ptr 管理资源时,特殊情况下,如果多个 shared_ptr 通过成员变量相互引用,就会形成循环引用,导致引用计数永不为 0,资源无法释放,进而引发内存泄漏问题。weak_ptr 通过不增加引用计数的方式,帮助打破这种循环引用。
以下是循环引用问题的一个典型场景:
#include <iostream>
#include <memory>
using namespace std;
struct ListNode {
int _data;
std::shared_ptr<ListNode> _next; // 下一个节点
std::shared_ptr<ListNode> _prev; // 上一个节点
~ListNode() { cout << "~ListNode()" << endl; }
};
int main() {
std::shared_ptr<ListNode> n1(new ListNode);
std::shared_ptr<ListNode> n2(new ListNode);
cout << "n1 use_count: " << n1.use_count() << endl; // 输出 1
cout << "n2 use_count: " << n2.use_count() << endl; // 输出 1
n1->_next = n2;
n2->_prev = n1;
cout << "n1 use_count after linking: " << n1.use_count() << endl; // 输出 2
cout << "n2 use_count after linking: " << n2.use_count() << endl; // 输出 2
return 0;
}
n1 的 _next 成员指向 n2,因此 n2 的引用计数增加。n2 的 _prev 成员指向 n1,因此 n1 的引用计数增加。n1 和 n2 的引用计数都为 2。n1 和 n2 离开作用域,它们的引用计数永远不会为 0,因此资源无法释放。再次理解循环引用:
_next 管着呢,_next 析构后,右边的节点就释放了。_next 什么时候析构呢,_next 是左边节点的成员,左边节点释放,_next 就析构了。_prev 管着呢,_prev 析构后,左边的节点就释放了。_prev 什么时候析构呢,_prev 是右边节点的成员,右边节点释放,_prev 就析构了。至此逻辑上成功形成回旋镖似的循环引用,谁都不会释放就形成了循环引用,导致内存泄漏。把 ListNode 结构体中的 _next 和 _prev 改成 weak_ptr,weak_ptr 绑定到 shared_ptr 时不会增加它的引用计数,_next 和 _prev 不参与资源释放管理逻辑,就成功打破了循环引用,解决了这里的问题。
当 ListNode 中的 std::shared_ptr<ListNode> _next; 和 std::shared_ptr<ListNode> _prev; 互相引用时:
// 相互引用
n1->_next = n2;
n2->_prev = n1;
调用成员函数:
shared_ptr(const shared_ptr<T>& sp) : _ptr(sp._ptr), _pcount(sp._pcount), _del(sp._del) {
++(*_pcount);
}
由此导致离开作用域时,在 n1 和 n2 的析构函数中因为引用是 2 而只是将 _pcount 进行了 -1,没有进行内存释放,造成内存泄漏。
这并不是一个'循环'本身的问题,而是两个对象互相持有对方的 shared_ptr 导致引用计数形成了一个'闭环',通过引用计数的次数阻止了资源的释放。这种'互相指向'实际上形成了一个引用计数上的死锁,使得两个对象的引用计数都无法降到零,最终导致内存泄漏。
循环引用的根本原因:
shared_ptr相互引用时,引用计数永不为 0,资源无法释放。
weak_ptr 解决循环引用template<class T>
class weak_ptr {
public:
weak_ptr() {}
weak_ptr(const shared_ptr<T>& sp) : _ptr(sp.get()) {}
weak_ptr<T>& operator=(const shared_ptr<T>& sp) {
_ptr = sp.get();
return *this;
}
private:
T* _ptr = nullptr;
};
weak_ptr 构造时不支持绑定到资源,只支持绑定到 shared_ptr,绑定时不增加 shared_ptr 的引用计数,如此便可以解决循环引用的问题。
#include <iostream>
#include <memory>
using namespace std;
struct ListNode {
int _data;
std::weak_ptr<ListNode> _next; // 下一个节点
std::weak_ptr<ListNode> _prev; // 使用 weak_ptr 打破循环引用
~ListNode() { cout << "~ListNode()" << endl; }
};
int main() {
std::shared_ptr<ListNode> n1(new ListNode);
std::shared_ptr<ListNode> n2(new ListNode);
cout << "n1 use_count: " << n1.use_count() << endl; // 输出 1
cout << "n2 use_count: " << n2.use_count() << endl; // 输出 1
n1->_next = n2;
n2->_prev = n1;
cout << "n1 use_count after linking: " << n1.use_count() << endl; // 输出 1
cout << "n2 use_count after linking: " << n2.use_count() << endl; // 输出 2
return 0;
}
_prev 改为 weak_ptr,不增加 n1 的引用计数。n2 的 _prev 并不阻止 n1 的释放,进而正常释放资源。weak_ptr 的特性与用法weak_ptr 最常见的应用场景是配合 shared_ptr 解决循环引用问题。
shared_ptr 互相引用时,引用计数永不为 0,导致资源无法释放。weak_ptr 的作用:
weak_ptr 代替某些 shared_ptr,让引用关系不增加引用计数,从而打破循环引用。shared_ptr 的资源管理,弱引用不参与资源的生命周期控制。weak_ptr 的另一个重要作用是提供一种非强引用,在某些场景下,允许观察资源而不拥有资源。
weak_ptr,可以在观察者中引用被观察者,同时不影响被观察者的生命周期。weak_ptr 引用缓存中的资源,资源被 shared_ptr 管理。weak_ptr 会变为过期状态,避免无效访问。weak_ptr 提供了一些独特的功能,可以用来检查资源的状态:
expired():
shared_ptr 是否已经释放资源。use_count():
shared_ptr 的引用计数。lock():
shared_ptr 来访问资源。shared_ptr 是空对象,确保访问安全。#include <iostream>
#include <memory>
using namespace std;
int main() {
std::shared_ptr<string> sp1(new string("Hello"));
std::weak_ptr<string> wp = sp1;
cout << "sp1 use_count: " << sp1.use_count() << endl; // 输出 1
cout << "wp expired: " << wp.expired() << endl; // 输出 0(未过期)
sp1 = std::make_shared<string>("World");
cout << "sp1 use_count: " << sp1.use_count() << endl; // 输出 1
cout << "wp expired: " << wp.expired() << endl; // 输出 1(已过期)
if (auto sp2 = wp.lock()) {
cout << "Accessed resource: " << *sp2 << endl;
} else {
cout << "Resource expired, cannot access." << endl;
}
return 0;
}
sp1 use_count: 1
wp expired: 0
sp1 use_count: 1
wp expired: 1
Resource expired, cannot access.
weak_ptr 不支持 RAII,仅观察资源,不参与资源管理。
相比直接使用裸指针(如 raw pointer)观察资源,weak_ptr 提供了更安全的方式:
weak_ptr 会自动检测到过期状态,避免了悬空指针问题。lock() 获取 shared_ptr,在访问前确保资源有效。weak_ptr 的作用不仅仅是解决引用计数的问题,它的主要功能包括:
shared_ptr 循环引用导致的内存泄漏。weak_ptr 是一种辅助工具,用于在某些需要非强引用的场景中增强程序的健壮性和资源管理的灵活性。
shared_ptr 的线程安全问题与解决方案shared_ptr 的引用计数是共享资源管理的重要机制,当多个 shared_ptr 实例同时管理同一资源时,引用计数的增减需要是线程安全的,否则可能导致资源重复释放或资源未释放等问题。
shared_ptr 的引用计数(use_count)在堆上存储。shared_ptr 的引用计数进行增减操作时,如果操作不是线程安全的,可能导致以下问题:
shared_ptr:shared_ptr 使用了原子操作(如 std::atomic)来保证引用计数的线程安全,允许多个线程同时拷贝或销毁 shared_ptr。shared_ptr 仅保证其自身的引用计数是线程安全的,并不保证管理的资源是线程安全的。shared_ptr 管理的对象(即资源本体)并不是线程安全的。std::mutex)等机制实现资源的同步。shared_ptr 引用计数的线程安全以下代码模拟了自定义 shared_ptr 的实现,并展示如何通过原子操作(std::atomic)或互斥锁(std::mutex)解决线程安全问题。
#include <iostream>
#include <thread>
#include <memory>
#include <mutex>
using namespace std;
struct AA {
int _a1 = 0;
int _a2 = 0;
~AA() { cout << "~AA()" << endl; }
};
template<typename T>
class SharedPtr {
public:
SharedPtr(T* ptr = nullptr) : _ptr(ptr), _ref_count(new int(1)) {}
SharedPtr(const SharedPtr& other) : _ptr(other._ptr), _ref_count(other._ref_count) {
++(*_ref_count); // 非线程安全
}
~SharedPtr() {
if (--(*_ref_count) == 0) { // 非线程安全
delete _ptr;
delete _ref_count;
}
}
T* operator->() { return _ptr; }
T& operator*() { return *_ptr; }
int use_count() const { return *_ref_count; }
private:
T* _ptr;
int* _ref_count;
};
此代码中的引用计数 (_ref_count) 不是线程安全的,当多个线程同时对 SharedPtr 进行拷贝或销毁时,可能导致未定义行为。
std::atomic 确保引用计数线程安全将 int* _ref_count 替换为 std::atomic<int>* _ref_count:
template<typename T>
class SharedPtr {
public:
SharedPtr(T* ptr = nullptr) : _ptr(ptr), _ref_count(new std::atomic<int>(1)) {}
SharedPtr(const SharedPtr& other) : _ptr(other._ptr), _ref_count(other._ref_count) {
_ref_count->fetch_add(1); // 线程安全的 ++
}
~SharedPtr() {
if (_ref_count->fetch_sub(1) == 1) { // 线程安全的 --
delete _ptr;
delete _ref_count;
}
}
T* operator->() { return _ptr; }
T& operator*() { return *_ptr; }
int use_count() const { return _ref_count->load(); }
private:
T* _ptr;
std::atomic<int>* _ref_count;
};
fetch_add(1) 和 fetch_sub(1) 是原子操作,确保多线程环境下引用计数的正确性。SharedPtr 进行拷贝或销毁,也不会破坏引用计数。在一些特殊场景下,可以使用 std::mutex 来保护引用计数:
template<typename T>
class SharedPtr {
public:
SharedPtr(T* ptr = nullptr) : _ptr(ptr), _ref_count(new int(1)), _mtx(new std::mutex) {}
SharedPtr(const SharedPtr& other) : _ptr(other._ptr), _ref_count(other._ref_count), _mtx(other._mtx) {
std::lock_guard<std::mutex> lock(*_mtx);
++(*_ref_count);
}
~SharedPtr() {
std::lock_guard<std::mutex> lock(*_mtx);
if (--(*_ref_count) == 0) {
delete _ptr;
delete _ref_count;
delete _mtx;
}
}
T* operator->() { return _ptr; }
T& operator*() { return *_ptr; }
int use_count() const {
std::lock_guard<std::mutex> lock(*_mtx);
return *_ref_count;
}
private:
T* _ptr;
int* _ref_count;
std::mutex* _mtx; // 保护引用计数的互斥锁
};
std::mutex 确保引用计数的增减操作是线程安全的。std::atomic,因为互斥锁的开销更大。即使 shared_ptr 的引用计数是线程安全的,资源本体的线程安全性需要外部保证。例如:
#include <iostream>
#include <memory>
#include <thread>
#include <mutex>
using namespace std;
struct AA {
int _a1 = 0;
int _a2 = 0;
~AA() { cout << "~AA()" << endl; }
};
int main() {
shared_ptr<AA> p(new AA);
const size_t n = 100000;
mutex mtx;
auto func = [&]() {
for (size_t i = 0; i < n; ++i) {
shared_ptr<AA> copy(p); // 拷贝 shared_ptr,线程安全
{ unique_lock<mutex> lk(mtx); copy->_a1++; copy->_a2++; }
}
};
thread t1(func);
thread t2(func);
t1.join();
t2.join();
cout << p->_a1 << endl;
cout << p->_a2 << endl;
cout << p.use_count() << endl;
return 0;
}
200000 200000 1
std::mutex 确保资源的访问是线程安全的。shared_ptr 的引用计数(拷贝和销毁)由标准库自动保证线程安全。shared_ptr 使用 std::atomic 保证引用计数的线程安全。std::atomic 或 std::mutex。shared_ptr 不保证管理的资源本体是线程安全的,外部需要通过同步机制(如 std::mutex)保护资源的访问。std::shared_ptr 即可,它已经处理了引用计数的线程安全问题。内存泄漏指的是程序在运行过程中申请了内存资源,但由于程序设计疏忽或异常导致这些资源未被释放,从而使这部分内存资源无法再被使用或回收。
delete 或 free 释放。shared_ptr)时,两个或多个对象间形成循环引用,导致资源无法释放。int main() {
char* ptr = new char[1024 * 1024 * 1024]; // 申请 1GB 内存
cout << (void*)ptr << endl;
return 0; // 程序结束后操作系统回收内存
}
valgrind --leak-check=full ./your_program
g++ -fsanitize=address -g your_program.cpp -o your_program ./your_program
new 和 delete 配对,malloc 和 free 配对。std::unique_ptr 和 std::shared_ptr)自动管理内存资源。raw pointer(裸指针)。class ResourceGuard {
char* _data;
public:
ResourceGuard(size_t size) : _data(new char[size]) {}
~ResourceGuard() { delete[] _data; }
};
std::shared_ptr 时,避免循环引用。shared_ptr 替换为 std::weak_ptr,打破循环。void leak() {
char* data = new char[1024]; // 未调用 delete,导致内存泄漏
}
解决方案:
void no_leak() {
std::unique_ptr<char[]> data(new char[1024]); // 离开作用域时,智能指针会自动释放内存
}
#include <memory>
struct Node {
std::shared_ptr<Node> next;
~Node() { std::cout << "Node destroyed" << std::endl; }
};
void cyclic_leak() {
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();
n1->next = n2;
n2->next = n1; // 循环引用
}
解决方案:
std::weak_ptr 打破循环:struct Node {
std::shared_ptr<Node> next;
std::weak_ptr<Node> prev; // 使用 weak_ptr 避免循环引用
~Node() { std::cout << "Node destroyed" << std::endl; }
};
void no_cyclic_leak() {
auto n1 = std::make_shared<Node>();
auto n2 = std::make_shared<Node>();
n1->next = n2;
n2->prev = n1; // 打破循环引用
}
weak_ptr 避免循环引用。通过规范编码和正确使用工具,可以有效预防和解决内存泄漏问题,保障程序的稳定性和高效运行。

微信公众号「极客日志」,在微信中扫描左侧二维码关注。展示文案:极客日志 zeeklog
将字符串编码和解码为其 Base64 格式表示形式即可。 在线工具,Base64 字符串编码/解码在线工具,online
将字符串、文件或图像转换为其 Base64 表示形式。 在线工具,Base64 文件转换器在线工具,online
将 Markdown(GFM)转为 HTML 片段,浏览器内 marked 解析;与 HTML转Markdown 互为补充。 在线工具,Markdown转HTML在线工具,online
将 HTML 片段转为 GitHub Flavored Markdown,支持标题、列表、链接、代码块与表格等;浏览器内处理,可链接预填。 在线工具,HTML转Markdown在线工具,online
通过删除不必要的空白来缩小和压缩JSON。 在线工具,JSON 压缩在线工具,online
将JSON字符串修饰为友好的可读格式。 在线工具,JSON美化和格式化在线工具,online