C++ 智能指针全解析:从 RAII 到自定义删除器的内存管理艺术

微软技术分享
优质创作者: 编程框架技术领域
领域专家: 操作系统技术领域
2026-04-15 22:49:28

一、前言

智能指针是 C++11 标准引入的、用于自动化管理动态内存的工具,本质是封装了原始指针的类模板。它基于 RAII(资源获取即初始化) 机制,解决了原始指针手动管理内存易导致的内存泄漏、重复释放、野指针等问题,是现代 C++ 开发中管理堆内存的首选方案。

为什么引入智能指针呢?原始指针需要手动搭配 new/delete管理内存,极易出现问题:

如果忘记调用 delete 或者程序异常跳转导致我们写的delete未执行则会导致内存泄漏;重复释放同一块内存会导致程序崩溃;指向已释放内存的指针(野指针)会导致未定义行为。

比如这段代码:

double Divide(int a, int b)
{
    //当b == 0时抛出异常
        if (b == 0)
        {
            throw "Divide by zero condition!";
        }
        else
        {
            return (double)a / (double)b;
        }
}
void Func()
{
    // 这⾥可以看到如果发⽣除0错误抛出异常,另外下⾯的array和array2没有得到释放。

    // 所以这⾥捕获异常后并不处理异常,异常还是交给外⾯处理,这⾥捕获了再重新抛出去。

    // 但是如果array2 new的时候抛异常呢,就还需要套⼀层捕获释放逻辑,这⾥更好解决⽅案

    // 是智能指针,否则代码太戳了

    int* array1 = new int[10];
    int* array2 = new int[10];   // 抛异常呢

    try
    {
        int len, time;
        cin >> len >> time;
        cout << Divide(len, time) << endl;
    }
    catch (...)
    {
        cout << "delete []" << array1 << endl;
        cout << "delete []" << array2 << endl;
        delete[] array1;
        delete[] array2;
        throw; // 异常重新抛出,捕获到什么抛出什么

    }
    // ...
    cout << "delete []" << array1 << endl;
    delete[] array1;
    cout << "delete []" << array2 << endl;
    delete[] array2;
}

int main()
{
    try
    {
        Func();
    }
    catch (const char* errmsg)
    {
        cout << errmsg << endl;
    }
    catch (const exception& e)
    {
        cout << e.what() << endl;
    }
    catch (...)
    {
        cout << "未知异常" << endl;
    }
    return 0;
}

原生指针需要开发者手动调用 new分配、delete释放内存,但异常会彻底打断程序的正常执行流程,导致手动编写的 delete代码无法执行,直接造成内存泄漏。

在上述代码示例中主要有以下两种场景:

场景 1:业务逻辑抛出异常(除 0 错误)Func中 try 块调用 Divide,若输入除数为 0,Divide会抛出异常。如果没有手动添加 catch(...) 块捕获并释放,异常会直接跳出 Func函数,array1/array2后续的 delete代码完全不会执行,两块堆内存直接泄漏。代码中虽然加了 catch块手动释放,但这是 “补救式” 的,不是天然安全的。

景 2:内存分配本身抛出异常(new array2 失败)代码中 new array1成功后,执行 new array2时,若系统内存不足,会抛出 std::bad_alloc 异常。此时异常发生在 try 块之前,不会进入 catch块,array1 已经分配的内存完全没有机会被 delete,直接永久泄漏。这种情况是原生指针的 天生缺陷:分配过程中出现异常,前面已经分配的资源无法自动回收。

二、RAII机制

RAII是ResourceAcquisition Is Initialization的缩写,他是⼀种管理资源的类的设计思想,本质是 ⼀种利用对象生命周期来管理获取到的动态资源,避免资源泄漏,这里的资源可以是内存、文件指针、网络连接、互斥锁等等。RAII在获取资源时把资源委托给一个对象,接着控制对资源的访问, 资源在对象的生命周期内始终保持有效,最后在对象析构的时候释放资源,这样保障了资源的正常释放,避免资源泄漏问题。

简单来讲RAII机制就是把需要手动管理的资源(内存、文件、锁、网络连接等),交给一个栈上的对象去托管:

对象创建(构造)时 → 自动申请 / 获取资源对象销毁(析构)时 → 自动释放 / 关闭资源

因为 C++ 栈上的对象,离开作用域 {} 时,编译器会强制自动销毁(无论代码是正常结束、中途 return、还是抛出异常),所以资源永远不会泄漏。

三、智能指针

3.1 核心定义

智能指针 是 C++ 标准库定义于 <memory> 头文件中的类模板,其核心是通过 RAII(Resource Acquisition Is Initialization,资源获取即初始化) 机制封装原生指针,实现动态内存 / 资源的自动化生命周期管理,是现代 C++ 替代手动 new/delete 内存管理的标准方案。

智能指针本身是栈分配对象,利用 C++ 编译器自动调用栈对象析构函数的特性,在对象生命周期结束时自动释放托管的堆内存,从语言层面解决原生指针的内存泄漏、重复释放、野指针、异常不安全等问题。

C++ 标准定义四类智能指针,**std::auto_ptr **已废弃,现代 C++ 仅使用后三者:

类型所有权语义引用计数性能核心用途std::auto_ptr独占(有缺陷)无低C++98 遗留,C++11 弃用std::unique_ptr独占式(强语义)无零开销独占资源管理(首选)std::shared_ptr共享式有轻微开销多对象共享资源std::weak_ptr非拥有式弱引用无零开销解决 shared_ptr 循环引用
为什么要设计出这么多类型的智能指针呢?我们知道原生指针的拷贝操作默认是值拷贝(浅拷贝)多个指针变量可以存储完全相同的地址,指向同一块内存对象。换句话说,浅拷贝导致所有权被隐式复制这几个值相同的原生指针变量就同时拥有了同一块内存空间的“所有权”,多个指针对该动态分配资源都拥有管理权、生命周期控制权,以及最终的释放责任。一个指针释放该资源后它的所有权失效不影响其他指针对同一块内存空间的所有权,他们也可以进行释放最终就会导致重复释放。

所以,原生指针无法回答:谁负责释放?谁是真正的管理者?

这就好比多个人拿着同一把钥匙,却没人规定谁最后锁门、谁负责销毁钥匙。有人先锁了门,其他人不知情还继续用钥匙开门(野指针访问),最后所有人都去锁一次门(重复释放),;更极端的是,所有人都忘了锁门,房子永远敞开(内存泄漏)。

而智能指针的核心设计思想,就是给指针绑定所有权规则,让内存的生命周期和指针的生命周期强绑定,离开作用域自动释放,同时用语法强制规定:同一时间,一块内存只能有唯一管理者,或计数共享管理者,绝对不允许无规则的浅拷贝。

但现实中的业务场景千差万别,我们不可能用一种规则适配所有需求 —— 这就是必须设计多种智能指针的原因:

3.2 auto_ptr

auto_ptr是C++98时设计出来的智能指针,他的特点是拷贝时把被拷贝对象的资源的管理权转移给 拷贝对象,这是⼀个非常糟糕的设计,因为他会到被拷贝对象悬空,访问报错的问题,C++11设计 出新的智能指针后,强烈建议不要使用auto_ptr。其他C++11出来之前很多公司也是明令禁止使用这个智能指针的。

#include <iostream>
#include <memory> // auto_ptr 头文件
using namespace std;

int main() {
    // 1. 创建 auto_ptr,管理一块堆内存
    auto_ptr<int> p1(new int(100));

    // 2. 【关键】拷贝构造 p2,这一步会导致所有权转移!
    auto_ptr<int> p2 = p1;

    // 3. 致命问题:原对象 p1 已经悬空!访问会崩溃/未定义行为
    cout << "p1 是否为空: " << (p1.get() == nullptr ? "是" : "否") << endl;  // 是!
     *p1;  // 直接程序崩溃!野指针访问

    return 0;
}

还有一个更加危险的场景就是auto_ptr给函数传值传参会导致隐形拷贝,原有的管理对象会被悬空

#include <iostream>
#include <memory>
using namespace std;

void func(auto_ptr<int> p) {
    cout << "函数内: " << *p << endl;
} // 函数结束,p 销毁,内存被释放

int main() {
    auto_ptr<int> p1(new int(200));
    func(p1);  // 隐形拷贝,p1 所有权转移,悬空!

     *p1;  // 这里访问 p1,程序直接崩溃!
    return 0;
}

3.3 unique_ptr

unique_ptr是C++11设计出来的智能指针,他的名字翻译出来是唯⼀指针,。它规定:一块动态内存,有且只有一个智能指针拥有它,绝对不允许拷贝(禁止浅拷贝),只能转移所有权(移动语义)。如果不需要拷贝的场景就非常建议使用他

#include <memory>
using namespace std;

int main() {
    unique_ptr<int> p1(new int(10));

    // 错误!unique_ptr 禁止拷贝构造
    unique_ptr<int> p2 = p1;

    // 错误!禁止赋值
    unique_ptr<int> p3;
    p3 = p1;

    cout << *p1 << endl;  // 正常输出 10
}

当我们想要转移所有权的话可以使用移动语义:

#include <iostream>
#include <memory>
using namespace std;

int main() {
    unique_ptr<int> p1(new int(20));

    // 显式移动,转移所有权
    unique_ptr<int> p2 = move(p1);

    if (!p1) {
        cout << "p1 已悬空" << endl;
    }
    if (p2) {
        cout << "p2 接管: " << *p2 << endl;
    }
}

3.4 shared_ptr

有些业务必须让多个指针共同管理同一块内存(比如多个对象引用同一个数据、缓存、多线程共享资源),不能强行独占。

所以C++11引入了智能指针shared_ptr,它的特点是允许拷贝,内部用引用计数解决共享所有权的释放问题

每拷贝一次,引用计数 +1;每销毁一个,计数 -1;

  1. 只有当计数变为 0(最后一个管理者销毁)时,才会真正释放内存;
  2. 彻底解决 谁最后释放的问题:最后一个走的人锁门。

这里重点要看看shared_ptr是如何设计的,尤其是引用计数的设计

1.设置为类中的普通成员变量

如果设置为类中普通成员变量,那么类实例化出的对象与对象之间各自都有自己的引用计数彼此之间不可见。此时引用计数就无法体现共享所有权的含义,智能指针对资源的管理与原生指针无异。

2.设置为类中的静态成员变量

如果设置为类中的静态成员变量,静态成员变量内存上只有一份,在全局区 / 静态区存储,其属于整个类,不属于任何一个对象。看似没问题但是如果一个sp原先指向资源1中途指向资源2了,那么此时资源1与资源2就同时被一个引用计数管理了:

主要这里一份资源就需要⼀个引用计数,所以引用计数才用静态成员的方式是无法实现的,要解决这个问题就需要使用堆上动态开辟的方式,构造智能指针对象时来一份资源,就要new一个引用计数出来。多个shared_ptr指向资源时就++引用计数,shared_ptr对象析构时就--引用计数,引用计数减到0时代表当前析构的shared_ptr是最后一个管理资源的对象,则析构资源。

share_ptr的模拟实现:

namespace yue
{
    template<class T>
    class shared_ptr
    {
    public:
        shared_ptr(T* ptr)
            : _ptr(ptr)
            , _pcount(new atomic<int>(1))
        {}


        ~shared_ptr()
        {
            if (--(*_pcount) == 0)
            {
                delete _ptr;
                delete _pcount;
            }
        }

        shared_ptr(const shared_ptr<T>& sp)
            : _ptr(sp._ptr)
            , _pcount(sp._pcount)
        {
            (*_pcount)++;
        }

        shared_ptr<T>& operator=(const shared_ptr<T>& sp)
        {
            //if (this != &sp)
            //不能给自己赋值
            if (_ptr != sp._ptr)
            {
                if (--(*_pcount) == 0)
                {
                    delete _ptr;
                    delete _pcount;
                }

                _pcount = sp._pcount;
                _ptr = sp._ptr;
                ++(*_pcount);
            }

            return *this;
        }

        T& operator*()
        {
            return *_ptr;
        }

        T* operator->()
        {
            return _ptr;
        }

        int use_count()
        {
            return *_pcount;
        }
    private:
        T* _ptr;
        //int* _pcount;
        atomic<int>* _pcount;  // 原子操作
    };
};

3.4.1 循环引用

shared_ptr大多数情况下管理资源非常合适,支持RAII,也支持拷贝。但是在循环引用的场景下会使得资源没得到释放导致内存泄漏,所以我们要认识循环引用的场景和资源没释放的原因,并且学会使用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() << endl;
    cout << n2.use_count() << endl;
    n1->_next = n2;
    n2->_prev = n1;
    cout << n1.use_count() << endl;
    cout << n2.use_count() << endl;
    return 0;
}

在这里我们定义了两个share_ptr指针分别管理两个双向链表节点,然后两个节点之间相互指向:

那么此时share_ptr类型的n1与n2析构之后,会导致引用计数从2减少到1不会使得资源被释放。这是因为两个资源之间还被彼此两个节点的前驱与后继指针指向,这时会导致两个资源谁也释放不了。因为其中一个节点的释放依赖另一个节点的释放(此时它的引用计数才能为0),而另一个节点的释放反过来也依赖这个节点的释放,形成了循环引用彼此共存的关系导致内存泄漏。

3.4.2 make_shared

std::make_shared是 C++11 标准库 <memory> 中专门用来创建并初始化对象,同时返回一个 std::make_shared。用于安全、高效构造 std::make_shared 的标准工厂函数,其设计目标是解决直接使用 new 配合 shared_ptr构造时存在的异常安全漏洞、内存效率低下、代码冗余等问题,是现代 C++ 中管理共享所有权堆对象的首选方案。

它是 创建 shared_ptr最推荐、最标准的写法。

我们讲过shared_ptr中的引用计数会开辟一块单独的内存空间,那么此时shared_ptr对象的内存与引用计数的内存可能不会开辟在一起就会导致内存碎片的问题。而make_shared就完美解决了这个问题:其只会申请一次内存,把对象内存 + 引用计数内存合并分配在一块比 shared_ptr(new T)少一次内存分配

3.4.3 线程安全问题

shared_ptr的引用计数对象在堆上,如果多个shared_ptr对象在多个线程中,进行shared_ptr的拷贝析构时会访问修改引用计数,就会存在线程安全问题,所以shared_ptr引用计数是需要加锁或者 原子操作保证线程安全的。

shared_ptr指向的对象也是有线程安全的问题的,但是这个对象的线程安全问题不归shared_ptr 管,它也管不了,应该有外层使用shared_ptr的人进行线程安全的控制。

struct AA
{
    int _a1 = 0;
    int _a2 = 0;
    ~AA()
    {
        cout << "~AA()" << endl;
    }
};
int main()
{
    yue::shared_ptr<AA> p(new AA);
    const size_t n = 100000;
    mutex mtx;
    auto func = [&]()
    {
        for (size_t i = 0; i < n; ++i)
        {
            // 这⾥智能指针拷⻉会++计数
             yue::shared_ptr<AA> copy(p);
             {
                 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;
}

3.5 weak_ptr

weak_ptr不支持RAII,也不支持访问资源,所以我们看文档发现weak_ptr构造时不支持绑定到资 源,只支持绑定到shared_ptr,绑定到shared_ptr时,不增加shared_ptr的引用计数,那么就可以解决上述的循环引用问题。

 weak_ptr也没有重载operator*和operator->等,因为他不参与资源管理,那么如果他绑定的 shared_ptr已经释放了资源,那么他去访问资源就是很危险的。weak_ptr⽀持expired检查指向的 资源是否过期,use_count也可获取shared_ptr的引用计数,weak_ptr想访问资源时,可以调用lock返回一个管理资源的shared_ptr,如果资源已经被释放,返回的shared_ptr是一个空对象,如果资源没有释放,则通过返回的shared_ptr访问资源是安全的。

#include<iostream>
#include<memory>
#include<string>
using namespace std;
int main()
{
    std::shared_ptr<string> sp1(new string("111111"));
    std::shared_ptr<string> sp2(sp1);
    std::weak_ptr<string> wp = sp1;
    cout << wp.expired() << endl;
    cout << wp.use_count() << endl;


    // sp1和sp2都指向了其他资源,则weak_ptr就过期了
    sp1 = make_shared<string>("222222");
    cout << wp.expired() << endl;
    cout << wp.use_count() << endl;
    sp2 = make_shared<string>("333333");
    cout << wp.expired() << endl;
    cout << wp.use_count() << endl;

    wp = sp1;
    //std::shared_ptr<string> sp3 = wp.lock();
    //这里wp调用lock会产生一个shared_ptr sp3与sp1共同管理string
    auto sp3 = wp.lock();

    cout << wp.expired() << endl;
    cout << wp.use_count() << endl;
    *sp3 += "###";
    cout << *sp1 << endl;
    return 0;
}

用weak_ptr解决上面shared_ptr循环引用的代码如下:

#include<iostream>
#include<memory>
using namespace std;
struct ListNode
{
    int _data;
    //前驱与后继指针使用weak_ptr不会增加引用计数
    std::weak_ptr<ListNode> _next;
    std::weak_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() << endl;
    cout << n2.use_count() << endl;
    n1->_next = n2;
    n2->_prev = n1;
    cout << n1.use_count() << endl;
    cout << n2.use_count() << endl;
    return 0;
}

四、自定义删除器

智能指针析构时默认是进行delete释放资源,这也就意味着如果不是new出来的资源,交给智能指 针管理,析构时就会崩溃。智能指针支持在构造时给一个删除器,所谓删除器本质就是一个可调用对象,这个可调用对象中实现你想要的释放资源的方式,当构造智能指针时,给了定制的删除器, 在智能指针析构时就会调用删除器去释放资源。

比如最常用的,当我们使用new[]开辟资源后就必须使用delete[]来释放,如果使用delete就会导致资源释放不完整内存泄漏的问题,当然编译器会检查出现这种情况会直接报错。因为new[]的情况比较常见所以在智能指针实现了一个特化版本,在这个版本中析构会自动调用delete[]而不是delete。

#include<memory>
using namespace std;

struct Date
{
    int _year;
    int _month;
    int _day;
    Date(int year = 1, int month = 1, int day = 1)
        :_year(year)
        , _month(month)
        , _day(day)
    {}
    ~Date()
    {
        cout << "~Date()" << endl;
    }
};
int main()
{
    // 这样实现程序会崩溃
    //unique_ptr<Date> up1(new Date[10]);
    //shared_ptr<Date> sp1(new Date[10]);

    //智能指针特化了一个版本,这里会调用delete[]
    unique_ptr<Date[]> up1(new Date[3]);
    shared_ptr<Date[]> sp1(new Date[3]);
    return 0;
}

除了这些,有时候我们自己在对象中开辟了资源并将对象交给智能指针进行托管。或者是以下操作产生的资源:

malloc 分配的内存

  • 文件指针 
  • 套接字、句柄、锁、共享内存等
  • 需要特殊清理逻辑的对象

此时我们就必须给智能指针传递自定义删除器,当智能指针的生命周期结束会自动调用传进来的删除器完成资源的释放。

需要注意的是unique_ptr与shared_ptr都支持自定义删除器,但是他们的使用方法有所不同:unique_ptr 是在类模板参数支持的, shared_ptr 是构造函数参数支持的。下面我们来详细学习以下:

4.1 unique_ptr

在unique_ptr中删除器是模板参数是类型的一部分,语法格式:

template <class T, class Deleter = default_delete<T>>
class unique_ptr;

删除器的定义可以有三种方式,仿函数、函数指针与Lambda表达式。三者的传参方式如下:

1.函数指针

void free_int(int* p) {
    free(p);
}

// 声明方式
std::unique_ptr<int, void(*)(int*)> ptr((int*)malloc(sizeof(int)), free_int);

2.仿函数

使用仿函数 unique_ptr 可以不在构造函数传递,因为仿函数类型构造的对象直接就可以调用,这时与shared_ptr不同的地方之一。

struct FreeDeleter {
    void operator()(int* p) const {
        free(p);
    }
};

std::unique_ptr<int, FreeDeleter> ptr((int*)malloc(4));

3.Lamdba

auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
unique_ptr<Date, decltype(delArrOBJ)> up4(new Date[5], delArrOBJ);

这里**decltype** 是 C++11 引入的类型推导关键字,它可以返回表达式的精确类型。它与auto的区别是**decltype会返回表达式的精确类型而auto可能会导致类型退化,所以这里使用decltype**更准确。

4.2 shared_ptr

shared_ptr 的删除器不是模板参数,而是构造函数参数,存在控制块中。不需要再模板参数中指定删除器的类型而是在构造的时候直接传递:

1.函数指针

void free_int(int* p) {
    free(p);
}

// 声明方式
std::shared_ptr<int> ptr((int*)malloc(sizeof(int)), free_int);

2.仿函数

使用仿函数 shared_ptr 必须在构造函数传递:

struct FreeDeleter {
    void operator()(int* p) const {
        free(p);
    }
};

std::shared_ptr<int> ptr((int*)malloc(4),FreeDeleter());

3.Lamdba

auto delArrOBJ = [](Date* ptr) {delete[] ptr; };
shared_ptr<Date> up4(new Date[5], delArrOBJ);

//或者直接:
shared_ptr<Date> up4(new Date[5],[](Date* ptr) {delete[] ptr; } );

在上面的shared_ptr的模拟实现代码中我们没有加入删除器的部分,对于shared_ptr来讲用户传进来的删除其可能会有三种类型,所以要想将删除器加入进来就必须考虑类型统一的问题。C++11中的包装器就为我们提供了一种解决方法,包装器可以包装一切可调用对象包括Lambda,仿函数与函数指针消除类型不统一带来的影响。

如果用户不传递删除器那么删除器就会被默认初始化为delete:

namespace bit
{
    template<class T>
    class shared_ptr
    {
    public:
        shared_ptr(T* ptr)
            : _ptr(ptr)
            , _pcount(new atomic<int>(1))
        {}

        template<class D>
        shared_ptr(T* ptr, D del)
            : _ptr(ptr)
            , _pcount(new atomic<int>(1))
            , _del(del)
        {}

        ~shared_ptr()
        {
            if (--(*_pcount) == 0)
            {
                //delete _ptr;
                _del(_ptr);
                delete _pcount;
            }
        }

        shared_ptr(const shared_ptr<T>& sp)
            : _ptr(sp._ptr)
            , _pcount(sp._pcount)
        {
            (*_pcount)++;
        }

        shared_ptr<T>& operator=(const shared_ptr<T>& sp)
        {
            //if (this != &sp)
            if (_ptr != sp._ptr)
            {
                if (--(*_pcount) == 0)
                {
                    delete _ptr;
                    delete _pcount;
                }

                _pcount = sp._pcount;
                _ptr = sp._ptr;
                ++(*_pcount);
            }

            return *this;
        }

        T& operator*()
        {
            return *_ptr;
        }

        T* operator->()
        {
            return _ptr;
        }

        int use_count()
        {
            return *_pcount;
        }
    private:
        T* _ptr;
        //int* _pcount;
        atomic<int>* _pcount;  // 原子操作
        //删除器:
        function<void(T*)> _del = [](T* ptr) {delete ptr; };
    };
};

文章来源: https://blog.csdn.net/yue_2899799318/article/details/159736066
版权声明: 本文为博主原创文章,遵循CC 4.0 BY-SA 知识共享协议,转载请附上原文出处链接和本声明。


...全文
64 回复 打赏 收藏 转发到动态 举报
写回复
用AI写文章
回复
切换为时间正序
请发表友善的回复…
发表回复
内容概要:本文深入解析C++中的智能指针,重点围绕`unique_ptr`展开,介绍了其概念、作用及在实际开发中的应用。文章首先阐述了智能指针基于RAII机制自动管理内存的原理,解决了传统指针易导致的内存泄漏和野指针问题,并对比了`unique_ptr`、`shared_ptr`和`weak_ptr`三种智能指针的特点与适用场景。随后详细讲解了`unique_ptr`的初始化方式(`make_unique`与直接初始化)、所有权转移、常用操作(reset、release、get)及其作为函数参数与返回值的最佳实践。进一步探讨了`unique_ptr`如何管理数组对象和配合自定义删除处理非内存资源(如文件句柄)。最后通过一个动态对象管理系统的实战项目,展示了`unique_ptr`在复杂场景下的应用,并结合Valgrind等工具进行内存泄漏检测,验证系统的稳定性与安性。 适合人群:具备一定C++基础,熟悉指针与面向对象编程,希望提升资源管理能力与代码安性的中级开发者,尤其适合从事系统编程、嵌入式开发或高性能服务端开发的技术人员。 使用场景及目标:①掌握智能指针的基本原理与选型依据;②熟练运用`unique_ptr`实现资源的自动管理,避免内存泄漏;③在工厂模式、容管理、资源封装等场景中合理使用`unique_ptr`提升代码健壮性;④通过实战项目理解现代C++的资源管理范式。 阅读建议:学习过程中应结合代码示例动手实践,重点关注所有权语义与移动语义的应用,建议在不同编译环境下测试示例代码,并使用内存检测工具验证效果,以加深对智能指针自动释放机制的理解。
Buffer内存管理实战技巧 # C/C++ Buffer内存管理:从stdio.h到环形缓冲区的性能提升 在C/C++开发中,Buffer内存管理是优化I/O和高并发性能的核心,尤其在系统级应用和实时处理场景。本文件通过实战代码和深度解析,聚焦stdio.h FILE*读写缓冲、C++ STL stringstream字符缓冲和环形缓冲区(Ring Buffer)的无锁通信,帮助开发者减少系统调用、降低内存复制,并提升并发效率。 stdio.h FILE*缓冲优化通过设置合适的缓冲大小(如4MB,基于系统页大小调整)和模式(_IOFBF缓冲最佳),减少I/O系统调用。使用setvbuf自定义缓冲或系统分配缓冲,结合批量读写和手动刷新(fflush),提升文件操作性能。例如,在大型文件复制中,预分配缓冲并分块处理,避免频繁内存分配。C++ STL stringstream字符缓冲优化通过预分配内存(reserve()方法)、复用对象(str("")和clear()重置)和直接操作底层字符串,减少内存分配次数和转换开销,适用于字符串与数据类型的高效转换。 环形缓冲区实现无锁通信,使用std::atomic确保线程安,避免mutex开销,通过循环队列设计充分利用内存空间。优化策略包括预留空位置区分空满状态、使用memory_order_acquire/release保证数据一致性、批量读写减少原子操作次数,以及固定大小缓冲避免动态分配。这种结构适用于高并发场景下的进程间或线程间通信,显著提升数据吞吐量。 通用优化策略强调减少内存分配(预分配和复用)、降低内存复制(指针直接操作和move语义)、减少系统调用(缓冲和批量操作)和提高并发性能(无锁设计和原子操作)。最佳实践包括根据场景选择Buffer类型、合理设置大小、监控内存使用(如valgrind检测泄漏)和及时释放资源(RAIIC++中)。本文件以代码示例和性能分析,为C/C++开发者提供实用指南,助力构建高效、稳定的系统应用。
内存管理C++最令人切齿痛恨的问题,也是C++最有争议的问题,C++高手从中获得了更好的性能,更大的自由,C++菜鸟的收获则是一遍一遍的检查代码和对C++的痛恨,但内存管理C++中无处不在,内存泄漏几乎在每个C++程序中都会发生,因此要想成为C++高手,内存管理一关是必须要过的,除非放弃C++,转到Java或者.NET,他们的内存管理基本是自动的,当然你也放弃了自由和对内存的支配权,还放弃了C++超绝的性能。本期专题将从内存管理、内存泄漏、内存回收这三个方面来探讨C++内存管理问题。 内容预览: 1 内 存管理 1.1 C++内存管理详解 1.1.1 内存分配方式 1.1.1.1 分配方式简介 1.1.1.2 明确区分堆与栈 1.1.1.3 堆和栈究竟有什么区别? 1.1.2 控制C++的内存分配 1.1.2.1 重载局的new和delete操作符 1.1.2.2 为单个的类重载 new[ ]和delete[ ] 1.1.3 常见的内存错误及其对策 1.1.4 指针与数组的对比 1.1.4.1 修改内容 1.1.4.2 内容复制与比较 1.1.4.3 计算内存容量 1.1.5 指针参数是如何传递内存的? 1.1.6 杜绝“野指针” 1.1.7 有了malloc/free为什么还要new/delete? 1.1.8 内存耗尽怎么办? 1.1.9 malloc/free的使用要点 1.2 C++中的健壮指针和资源管理 1.2.1 第一条规则(RAII) 1.2.2 Smart Pointers 1.2.3 Resource Transfer 1.2.4 Strong Pointers 1.2.5 Parser 1.2.6 Transfer Semantics 1.2.7 Strong Vectors 1.2.8 Code Inspection 1.2.9 共享的所有权 1.2.10 所有权网络 2 内存泄漏 2.1 C++中动态内存分配引发问题的解决方案 2.2 如何对付内存泄漏? 2.3浅谈C/C++内存泄漏及其检测工具 2.3.1 内存泄漏的定义 2.3.2 内存泄漏的发生方式 2.3.3 检测内存泄漏 2.3.3.1 VC下内存泄漏的检测方法 2.3.3.2 使用BoundsChecker检测内存泄漏 2.3.3.3 使用Performance Monitor检测内存泄漏 3 探讨C++内存回收 3.1 C++内存对象大会战 3.1.1 基本概念 3.1.2 三种内存对象的比较 3.1.3 使用栈对象的意外收获 3.1.4 禁止产生堆对象 3.1.5 禁止产生栈对象 3.2 浅议C++ 中的垃圾回收方法

6,682

社区成员

发帖
与我相关
我的任务
社区描述
微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。
windowsmicrosoft 企业社区
社区管理员
  • 微软技术分享
  • 郑子铭
加入社区
  • 近7日
  • 近30日
  • 至今
社区公告

微软技术社区为中国的开发者们提供一个技术干货传播平台,传递微软全球的技术和产品最新动态,分享各大技术方向的学习资源,同时也涵盖针对不同行业和场景的实践案例,希望可以全方位地帮助你获取更多知识和技能。

予力众生,成就不凡!微软致力于用技术改变世界,助力企业实现数字化转型。

试试用AI创作助手写篇文章吧