it编程 > 编程语言 > C/C++

C++智能指针介绍

18人参与 2026-09-15 C/C++

前言:上一篇说到异常会让程序跳过后面的一部分代码,这就带来了一个问题:如果后面的代码刚好是释放内存的,那这块内存该怎么办呢?智能指针就是用来帮我们管理这类资源的。不过它也不是把普通指针换一个名字就完事了,拷贝以后由谁释放资源、多个对象能不能一起管理同一块空间,这些问题都需要考虑。下面就从这些地方来看看几种智能指针的使用和实现。

1.为什么需要智能指针

先来看一个很简单的例子:

void func()
{
    int* p = new int[10];
    cout << "申请内存成功" << endl;
    // 如果中途发生异常
    throw "发生异常";
    // 异常发生后,这句代码不会执行找出内存泄漏
    delete[] p;
}
int main()
{
    try
    {
        func();
    }
    catch (const char* errmsg)
    {
        cout << errmsg << endl;
    }
    return 0;
}

这里虽然写了delete[] p,但是执行到throw以后,程序就会跳到外层匹配的catch,不会再回来执行后面的释放语句。

函数退出时,局部变量p的生命周期结束了,但它只是一个保存地址的普通指针,**指针变量的销毁,不会自动释放它指向的堆内存。**所以申请出来的这块空间就没有被释放,出现了内存泄漏。

那在函数里面先捕获异常,把资源释放了,再重新抛出去不就可以了吗?

这个思路可以处理当前的问题,但资源多起来以后就比较麻烦了。比如连续申请两块空间,第一块申请成功,第二次new却抛出了异常,那么第一块仍然需要释放。正常退出、提前返回和不同位置抛异常,都要考虑哪些资源已经申请成功,哪些还没有申请,单靠在每条路径上补释放语句很容易遗漏。

我们之前学过,类对象离开作用域时会自动调用析构函数;异常向外传递、进行栈展开时,已经构造完成的局部对象也会被析构。那么可以把资源交给一个对象管理,等这个管理对象析构时,再由它完成资源的释放。

这就是raii的基本思路。raii是resource acquisition is initialization的缩写,通常叫作“资源获取即初始化”。理解时可以抓住一点:把资源的管理和对象的生命周期联系起来。

raii不只适用于内存,文件等需要配对获取和释放的资源也可以这样管理。后面用智能指针管理文件时,就会把释放动作换成fclose

智能指针在这个基础上还需要模拟指针的使用方式,比如重载operator*operator->,让我们仍然能通过解引用和箭头访问对象;数组形式还可以提供operator[]

所以它本质上是一个类对象,只不过内部保存了指针,并且替我们承担了相应的资源管理工作。这里还有一个很重要的问题:如果这个类对象被拷贝了,两个对象都保存同一个地址,析构时岂不是会释放两遍?不同智能指针的区别,很大一部分就在于如何处理这个问题。

2.标准库智能指针的使用

标准库智能指针放在<memory>中。下面这些例子共用的头文件是:

2.1 auto_ptr、unique_ptr与shared_ptr

先定义一个date类,析构时打印信息,方便观察对象什么时候被释放:

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()
{
    auto_ptr<date> ap1(new date);
    // 构造拷贝时,管理权转移到 ap2 上,导致了 ap1 悬空
    auto_ptr<date> ap2(ap1);
    //ap1->_year++;
    unique_ptr<date> up1(new date);
    // 不支持拷贝,但是支持移动但是移动后里面的
    // up1 会悬空所以要谨慎哦
    unique_ptr<date> up2(move(up1));
    // 它们共同管理一块空间的资源
    shared_ptr<date> sp1(new date);
    shared_ptr<date> sp2(sp1);
    // 这里可以取到这个资源被几个指针管理
    cout << sp1.use_count() << endl; 
    sp1->_year++;
    // 和上面一样同样会有悬空的问题
    shared_ptr<date> sp3(move(sp1));
    return 0;
}

先看auto_ptr。

auto_ptr是c++98中的智能指针。它处理拷贝的方式比较特殊:不是让两个对象共同管理资源,而是把原对象的管理权转移给新对象。

执行auto_ptr<date> ap2(ap1)以后,原来的date对象没有被拷贝,仍然是同一个对象,只是改由ap2管理,ap1内部的指针则被置空。

这时不能再通过ap1访问原对象。如果继续执行ap1->_year++,就会变成对空指针的解引用:

这里要分清楚,原来的资源并没有被提前释放,而是ap1已经不再指向它。上面的访问错误也不是前面通过throw主动抛出的c++异常。

这种“写的是拷贝,实际却把原对象的管理权拿走了”的行为不太符合我们平常对拷贝的理解,使用时容易出问题。auto_ptr在c++11中被弃用,在c++17中被移除,现在主要用来了解早期的设计思路。上面含有它的历史示例可以放在c++11环境中观察,不能要求所有c++17环境继续支持它。版本说明可以看auto_ptr文档

再看unique_ptr。

unique_ptr强调独占所有权,同一份资源在同一时刻由一个unique_ptr负责管理。它直接禁止拷贝构造和拷贝赋值,不让“两个管理对象到底谁来释放”这个问题通过普通拷贝产生。

但是独占不代表管理权永远不能转交。c++11引入移动语义以后,可以通过移动把管理权交给另一个对象。

在上面的unique_ptr<date> up2(move(up1))中,std::move使这里能够选择移动构造函数,真正转交资源的是移动构造函数。移动完成后,up2管理原来的dateup1变为空。

所以移动以后仍然不能继续解引用up1。它和auto_ptr的区别在于,转交管理权需要明确使用移动,而不是一次普通拷贝就把原对象变空了。

最后看shared_ptr。

有些场景下,确实需要多个对象共同管理一份资源,这时就可以使用shared_ptr。它支持拷贝,采用引用计数来记录同一组共享所有者的数量。

sp1刚创建时,引用计数为1;用sp1拷贝构造sp2以后,两者共同管理同一个date,引用计数变成2。因此sp1.use_count()输出2。

这里拷贝的是智能指针对象,不是重新创建一份date。所以通过sp1->_year++修改对象后,再通过sp2访问,看到的也是修改后的结果。

shared_ptr也支持移动。执行shared_ptr<date> sp3(move(sp1))时,sp1原来持有的那一份管理权转交给sp3sp1变为空。这次移动没有多增加一位所有者,原对象仍由sp2sp3共同管理。

等某个shared_ptr析构时,先减少一次引用计数;只有最后一位所有者离开、计数降到0时,才真正释放资源。

上面这段代码的输出为:

2
~date()
~date()
~date()

虽然定义了多个智能指针对象,但总共只申请了三个date,最终这三个对象各析构一次。

还有一个weak_ptr,它不拥有被观察的对象,不会增加这里的引用计数。它主要用来配合shared_ptr,后面讲循环引用时再具体看。

2.2数组管理与定制删除器

前面的资源都是通过new date创建的单个对象,使用默认的delete释放就可以了。但资源的释放方式不只有这一种。

new[]申请的数组应该用delete[]malloc申请的空间应该用freefopen打开的文件应该用fclose。智能指针不会根据一个普通地址自动猜出它是如何申请的,释放方式需要匹配。

数组可以使用数组形式的智能指针:

unique_ptr<date[]>对应数组管理,默认释放时使用delete[]unique_ptr<t[]>从c++11就可以使用,而下面的shared_ptr<date[]>

除了使用数组形式,还可以提供一个定制删除器。删除器本质上就是一个可调用对象,把资源的释放动作写在里面,智能指针需要释放资源时,再调用它。

先准备仿函数、普通函数和关闭文件的仿函数:

template<class t>
class deletearray
{
public:
    void operator()(t* ptr)
    {
        delete[] ptr;
    }
};
template<class t>
void deletearrayfunc(t* ptr)
{
    delete[] ptr;
} 
class fclose
{
public:
    void operator()(file* ptr)
    {
        cout << "fclose:" << ptr << endl;
        fclose(ptr);
    }
};

然后把它们交给智能指针:

int main()
{
    // 对于 new[] 这样会报错
    /*unique_ptr<date> up1(new date[10]);
    shared_ptr<date> up1(new date[10]);*/
    // 但是库里提供了一个特化版本的,本质上
    // 是在析构时调用delete[]
    unique_ptr<date[]> up1(new date[5]);
    shared_ptr<date[]> sp1(new date[5]);
    // 也可以使用仿函数对象做删除器
    // unique_ptr是在类模板参数⽀持的
    unique_ptr<date, deletearray<date>> up2(new date[5]);
    // shared_ptr是构造函数参数⽀持的
    shared_ptr<date> sp2(new date[5], deletearray<date>());
    // 一些可调用对象作为删除器
    // 函数指针做删除器
    unique_ptr<date, void(*)(date*)> up3(new date[5], deletearrayfunc<date>);
    shared_ptr<date> sp3(new date[5], deletearrayfunc<date>);
    // lambda表达式做删除器
    auto delarrobj = [](date* ptr) { delete[] ptr; };
    unique_ptr<date, decltype(delarrobj)> up4(new date[5], delarrobj);
    shared_ptr<date> sp4(new date[5], delarrobj);
    // 实现其他资源管理的删除器
    shared_ptr<file> sp5(fopen("main.cpp", "r"), fclose());
    shared_ptr<file> sp6(fopen("main.cpp", "r"), [](file* ptr) {
        cout << "fclose:" << ptr << endl;
        fclose(ptr);
        });
    return 0;
}

几种写法虽然不同,但最终做的都是同一件事:让数组通过delete[]释放。

使用删除器时,unique_ptrshared_ptr的接口有一点区别:

deletearray<date>是一个仿函数类型,可以默认构造出对象,因此up2只传资源指针也可以使用默认构造出来的删除器。而up3用函数指针作为删除器,就同时传入具体的函数地址,也不知道为什么要设置成两个不同的方式,总感觉有些混乱的感觉

lambda表达式的类型没有一个方便直接写出来的名字,所以先用delarrobj保存lambda对象,再用decltype(delarrobj)取得类型,放到unique_ptr的第二个模板参数中:

再看最后两个文件对象,就能发现智能指针管理的并不一定是new出来的内存。sp5sp6负责管理file*,资源释放时分别调用仿函数和lambda中的fclose

2.3make_shared与判空

shared_ptr除了接收裸指针,还可以通过make_shared创建:

int main()
{
    shared_ptr<date> sp1(new date(2024, 9, 11));
    shared_ptr<date> sp2 = make_shared<date>(2024, 9, 11);
    auto sp3 = make_shared<date>(2024, 9, 11);
    shared_ptr<date> sp4;
    // 本质上是调用这个
    // if (sp1.operator bool())
    if (sp1)
        cout << "sp1 is not nullptr" << endl;
    if (!sp4)
        cout << "sp1 is nullptr" << endl;
    // 不能这样,因为本质上是不同的类也没有支持它们
    // 之间的转化
    //shared_ptr<date> sp5 = new date(2024, 9, 11);
    //unique_ptr<date> sp6 = new date(2024, 9, 11);
    return 0;
}

make_shared<date>(2024, 9, 11)会创建一个date,并把后面的参数传给它的构造函数,最后返回管理这个对象的shared_ptr<date>

所以这里sp1sp2sp3分别管理三个独立创建的对象。构造参数相同不代表它们共享同一份资源,只有建立了共享所有权关系,才会共用同一组引用计数。

shared_ptrunique_ptr都提供了operator bool,因此可以直接放进if判断所保存的指针是否非空。上面sp1保存着有效指针,会进入第一个分支;sp4默认构造为空,会进入if (!sp4)分支。

资源交给智能指针后,也不要再手动释放它。对于shared_ptr,共享所有权应该通过已有的shared_ptr建立,不能把同一个裸指针分别交给两个独立构造的shared_ptr,否则它们各自计数,最后可能重复释放同一个对象。

3.智能指针的模拟实现

3.1auto_ptr

最简单的资源管理方式,就是内部保存一个t*,构造时接管它,析构时释放它,再通过运算符重载模拟指针的行为:

template<typename t>
	class auto_ptr
	{
	public:
		// explicit 用来防止隐式类型转化
		explicit auto_ptr(t* ptr = nullptr)
			:_ptr(ptr)
		{ }
		auto_ptr(auto_ptr<t>& ap)
		{
			_ptr = ap._ptr;
			ap._ptr = nullptr;
		}
		~auto_ptr()
		{
			delete _ptr;
		}
		auto_ptr<t>& operator=(auto_ptr<t>& ap)
		{
			// 防止给自己赋值
			if (this != &ap)
			{
				if (_ptr)
					delete _ptr;
				_ptr = ap._ptr;
				ap._ptr = nullptr;
			}
			return *this;
		}
		// 模拟指针行为
		t& operator*()
		{
			return *_ptr;
		}
		t* operator->()
		{
			return _ptr;
		}
	private:
		t* _ptr;
	};

这里普通构造和析构比较直接。构造函数把传进来的地址保存到_ptr,析构函数执行delete _ptrdelete空指针不会释放任何对象,因此转移管理权后,原来的智能指针也可以正常析构。

operator*返回所指对象的引用,operator->返回内部指针,这样外面就可以像使用普通指针一样访问对象。

重点是拷贝构造。它先把ap._ptr交给当前对象,再把ap._ptr置空,这就完成了管理权转移。注意参数不是const auto_ptr<t>&,因为这里需要修改被拷贝的对象。

赋值比拷贝构造多一步:赋值对象原来可能已经管理着一份资源,需要先处理旧资源,再接管新资源。

所以先判断是不是自己给自己赋值;不是的话,释放当前的_ptr,接收ap._ptr,最后把ap置空。整个过程中,同一份资源仍然只有一个负责释放它的对象。

3.2unique_ptr

unique_ptr的基础部分相同,变化主要在拷贝和移动上:

template<typename t>
	class unique_ptr
	{
	public:
		explicit unique_ptr(t* ptr = nullptr)
			:_ptr(ptr)
		{ }
		// 禁用左值拷贝构造
		unique_ptr(const unique_ptr<t>& up) = delete;
		unique_ptr(unique_ptr<t>&& up)
		{
			_ptr = up._ptr;
			up._ptr = nullptr;
		}
		// 禁用左值赋值重载
		unique_ptr<t>& operator=(const unique_ptr<t>& up) = delete;
		unique_ptr<t>& operator=(unique_ptr<t>&& up)
		{
			if (this != &up)
			{
				delete _ptr;
				_ptr = up._ptr;
				up._ptr = nullptr;
			}
			return *this;
		}
		~unique_ptr()
		{
			delete _ptr;
		}
		t& operator*()
		{
			return *_ptr;
		}
		t* operator->()
		{
			return _ptr;
		}
	private:
		t* _ptr;
	};

这里通过= delete明确禁用了拷贝构造和拷贝赋值。注意它是告诉编译器“这个函数不能调用”,和释放内存的delete _ptr不是一个意思。

移动构造接收unique_ptr<t>&&,先接管原指针,再把原对象置空。移动赋值还要考虑当前对象已有资源,所以先释放自己的旧资源,再接管对方的资源。

3.3shared_ptr

shared_ptr不能在每个对象析构时直接delete _ptr,因为别的共享所有者可能还要继续使用这份资源。它需要知道:当前到底还有几个智能指针在共同管理它。

这里的引用计数要跟着“这一份被共同管理的资源”走,而不是跟着某个独立的智能指针对象走。

如果每个对象都保存一份独立的整数,拷贝后修改的就只是各自的计数;如果使用类的静态成员,同类型的不同资源又会被混到同一个计数里。因此这里单独申请一个计数器,让同一组共享对象都保存指向它的指针。

完整的类定义如下:

template<typename t>
	class shared_ptr
	{
	public:
		explicit shared_ptr(t* ptr = nullptr)
			:_ptr(ptr)
			,_pcount(ptr ? new int(1) : nullptr)
		{ }
		template<typename d>
		explicit shared_ptr(t* ptr, d del)
			:_ptr(ptr)
			,_pcount(ptr ? new int(1) : nullptr)
			,_del(del)
		{ }
		shared_ptr(const shared_ptr<t>& sp)
			:_ptr(sp._ptr)
			, _pcount(sp._pcount)
			, _del(sp._del)
		{
			if (_pcount)
				++(*_pcount);
		}
		void cheke_release()
		{
			// 计数器为0时释放资源
			if (_pcount && --(*_pcount) == 0)
			{
				_del(_ptr);
				delete _pcount;
				_ptr = nullptr;
				_pcount = nullptr;
			}
		}
		shared_ptr<t>& operator=(const shared_ptr<t>& sp)
		{
			if (this != &sp)
			{
				cheke_release();
				_ptr = sp._ptr;
				_pcount = sp._pcount;
				if (_pcount)
					++(*_pcount);
				_del = sp._del;
			}
			return *this;
		}
		~shared_ptr()
		{
			cheke_release();
		}
		t* get() const
		{
			return _ptr;
		}
		int use_count() const
		{
			return _pcount ? *_pcount : 0;
		}
		t& operator*()
		{
			return *_ptr;
		}
		t* operator->()
		{
			return _ptr;
		}
	private:
		t* _ptr;
		int* _pcount; // 计数器
		function<void(t*)> _del = [](t* ptr) { delete ptr; };
	};

普通构造时,如果传入的指针非空,就创建一个值为1的计数器;如果没有管理资源,_pcount也为空。use_count()根据这一点返回实际计数或0。

拷贝构造时,_ptr_pcount都从原对象中复制过来,所以两个智能指针不只指向相同资源,也指向同一个计数器。再对这个计数器加1,共享关系就建立起来了。

删除器也要一起保存。因为最后释放资源的是哪一个shared_ptr,取决于它们的生命周期,不能假定一定由最开始创建的对象负责释放。这里使用前面学过的function<void(t*)>来保存可调用对象,通过_del(_ptr)执行最终的释放动作。

接着看cheke_release()。这个函数先减少一次引用计数,表示当前智能指针不再管理这份资源:

析构函数直接调用它,而赋值运算符要把“离开原来的共享关系”和“加入新的共享关系”连起来做。

假设当前对象原来和另外两个智能指针共享资源a,现在改成共享资源b,那么赋值时先把a的计数减1,不能直接删除a,因为另外两个对象还在使用它;然后接收b的资源地址、计数器地址和删除器,并把b的计数加1。

this != &sp处理自己给自己赋值的情况。如果是两个不同的智能指针对象,但本来就在同一组共享关系中,这份实现会先减1再加1,最终计数不变。

观察效果:

// 测试
int main()
{
    jy::auto_ptr<date> ap1(new date);
    jy::auto_ptr<date> ap2(ap1);
    jy::unique_ptr<date> up1(new date);
    jy::unique_ptr<date> up3(move(up1));
    jy::shared_ptr<date> sp1(new date);
    jy::shared_ptr<date> sp2(sp1);
    jy::shared_ptr<date> sp3(sp2);
    cout << sp1.use_count() << endl;
    sp1->_year++;
    cout << sp1->_year << endl;
    cout << sp2->_year << endl;
    cout << sp3->_year << endl;
    return 0;
}

运行结果为:

3
2
2
2
~date()
~date()
~date()

sp1sp2sp3共同管理一个date,所以引用计数为3。date默认年份是1,通过sp1加1以后,再从三个智能指针访问,得到的都是2。

最后仍然只析构了三个date对象:一个由auto_ptr管理,一个由unique_ptr管理,一个由这三个shared_ptr共同管理。共享的是资源和计数,不是每拷贝一个智能指针就多创建一个资源对象。

4.shared_ptr与weak_ptr

4.1循环引用为什么会造成内存泄漏

引用计数能解决共享资源的释放问题,但如果共享关系形成了环,就可能出现所有对象都已经不再需要,资源却没有释放的情况。

用两个链表节点就能说明这个问题。假设节点a的_next和节点b的_prev都是shared_ptr,a通过_next拥有b,b又通过_prev拥有a。

刚创建节点时,外部的n1n2分别管理一个节点,各自的引用计数都是1。把节点连起来以后,a除了被n1管理,还被b中的_prev管理;b除了被n2管理,还被a中的_next管理,因此两个计数都变成2。

n1n2离开作用域,各自的计数确实会减1,但减完以后仍然是1:

  1. a要被释放,需要b里面的_prev先放弃对它的管理。
  2. _prev是b的成员,b析构时才会跟着析构。
  3. b要被释放,又需要a里面的_next先放弃对它的管理。
  4. _next是a的成员,只有a析构时才会跟着析构。

这样就形成了循环,谁也无法成为第一个被释放的节点。这里的问题是循环强引用让计数无法归零,造成内存泄漏

既然只是想记录另一个节点的位置,能不能不把这条连接也算成一份所有权呢?这就是weak_ptr的作用。

weak_ptr可以观察shared_ptr管理的对象,但不会增加强引用计数,也不会因为自己还存在,就要求对象继续存活。

下面把节点里的连接改成weak_ptr

struct listnode
{
    int _data;
    //std::shared_ptr<listnode> _next;
    //std::shared_ptr<listnode> _prev;
    // 这里改成weak_ptr时
    // 不增加n2的引用计数,不参与资源释放的管理,就不会形成循环引用了
    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;
}

这里实际启用的两个成员都是weak_ptr,所以n1->_next = n2n2->_prev = n1只是建立观察关系,没有增加节点的强引用计数。

对应的输出是:

1
1
1
1
~listnode()
~listnode()

前后四次计数都是1,最后两个节点也正常析构了。对于这个例子,节点生命周期由外部的n1n2负责,节点间的两个成员只负责记录连接关系,不负责延长对方的生命周期。

4.2weak_ptr如何访问资源

weak_ptr不保证被观察的对象一直活着,因此它没有提供像shared_ptr那样直接访问对象的operator*operator->。如果直接保存一个地址就去解引用,那么真正的所有者已经释放资源时,就有可能访问到失效内存。

标准库的weak_ptr主要通过下面三个接口配合使用:

接口作用
use_count()返回所观察对象的强引用计数,不是weak_ptr自身的数量
expired()判断所观察的对象是否已经没有共享所有者
lock()对象仍存活时,取得一个共享所有权的shared_ptr;已经过期时,返回空shared_ptr

具体行为可以对照weak_ptr接口文档。真正访问对象时,先取得lock()返回的shared_ptr,再判断它是否为空。

int main()
{
    shared_ptr<string> sp = make_shared<string>("hello");
    // weak_ptr观察sp管理的资源,不增加引用计数
    weak_ptr<string> wp = sp;
    cout << "引用计数:" << wp.use_count() << endl;
    cout << "是否过期:" << wp.expired() << endl;
    // 通过lock获取shared_ptr,安全访问资源
    shared_ptr<string> tmp = wp.lock();
    if (tmp)
    {
        cout << *tmp << endl;
    }
    // 释放所有shared_ptr
    sp.reset();
    tmp.reset();
    cout << "是否过期:" << wp.expired() << endl;
    return 0;
}

开始时只有sp拥有这个字符串对象,因此引用计数为1。wp绑定它以后没有增加计数,此时对象仍然存在,expired()返回false,输出为0。

随后wp.lock()成功,返回一个与sp共享所有权的shared_ptr,保存在tmp中。这时才新增了一份共享所有权,强引用计数变成2。通过tmp就可以访问字符串,输出hello

注意lock()不会把wp本身变成一个shared_ptr,它返回的是另一个对象,wp仍然只是观察者。

后面先执行sp.reset()sp放弃所有权,但是tmp还在,因此字符串还不会销毁。再执行tmp.reset(),最后一个共享所有者也放弃了所有权,字符串对象才被销毁。

此时wp虽然还在,但它不能把字符串留住,expired()返回true。完整输出为:

引用计数:1
是否过期:0
hello
是否过期:1

写一个非常简化的版本:

template<typename t>
	class weak_ptr
	{
	public:
		weak_ptr()
		{ }
		weak_ptr(shared_ptr<t>& ptr)
			:_ptr(ptr.get())
		{ }
		weak_ptr<t>& operator=(shared_ptr<t>& ptr)
		{
			_ptr = ptr.get();
			return *this;
		}
	private:
		t* _ptr;
	};

构造和赋值时,它通过get()取得地址并保存下来,没有增加计数,也没有负责释放目标资源的析构逻辑。

实际上想要实现一个完整可用的weak_ptr比较复杂前面的两个指针也要大改,我这里就只是像体现一下这个指针的思想

5.智能指针的发展与内存泄漏

5.1c++11与boost中的智能指针

智能指针并不是到c++11才突然出现的。c++98就有了auto_ptr,而在c++11之前,boost也提供了多种智能指针,用来处理不同的资源管理需求。

boost可以理解成一组为c++提供扩展功能的库,其中一些成熟设计也为标准库的发展提供了参考。智能指针这部分的关系大致是:

unique_ptrboost::scoped_ptr都可以用于独占管理,但两者不能简单理解成完全相同的类。scoped_ptr强调在当前作用域内管理资源,不提供所有权转移语义;unique_ptr则支持通过移动转移所有权。相关背景可以看boost.smartptr的介绍与历史

5.2内存泄漏的危害、检测与避免

内存泄漏不是内存在物理上消失了,而是程序申请的内存已经不再需要,却没有按预期释放,继续占着资源。原因可能是忘记释放,也可能是异常跳过释放语句,或者像前面的循环引用一样,资源管理关系出了问题。

普通的小程序运行一会儿就退出,进程结束后,操作系统通常会回收它占用的进程内存,所以问题有时不容易被注意到。但是长期运行的程序不能依靠“等退出再回收”:每次泄漏一点,累积起来就会占用越来越多的资源,影响运行,严重时会导致内存分配失败。

检测内存泄漏需要借助工具。例如linux环境中可以使用valgrind的memcheck,windows下也有visual leak detector这类工具。工具可以帮助定位未释放内存及相关分配位置,使用时还要确认它与编译器、运行环境的兼容情况。

不过检测是发现问题,设计和编码时仍然要尽量避免产生问题,想要达到这点就多练习吧

到此这篇关于c++智能指针介绍的文章就介绍到这了,更多相关c++智能指针内容请搜索代码网以前的文章或继续浏览下面的相关文章希望大家以后多多支持代码网!

(0)

您想发表意见!!点此发布评论

推荐阅读

C++指针的使用详解

09-15

深入理解C++ explicit关键字

09-15

C语言字符数组和字符串的区别是什么?一文搞懂\0的作用

09-14

ubuntu虚拟机安装QT找不到图标?打开方式实现教程

09-13

VSCode C++多文件编译解决undefined reference

09-18

浅谈C++ 获取键盘按键的 N 种方式

09-18

猜你喜欢

版权声明:本文内容由互联网用户贡献,该文观点仅代表作者本人。本站仅提供信息存储服务,不拥有所有权,不承担相关法律责任。 如发现本站有涉嫌抄袭侵权/违法违规的内容, 请发送邮件至 2386932994@qq.com 举报,一经查实将立刻删除。

发表评论