昆明网站开发,做外贸翻译用哪个网站好,企业建设网站注意点,网站微信建设方案文章目录 前言一、智能指针的发展历史1.C 98/03 的尝试——std::auto_ptr2.std::unique_ptr3.std::shared_ptr4.std::weak_ptr5.智能指针的大小6.智能指针使用注意事项 二、智能指针的模拟实现三、C11和boost中智能指针的关系 前言
C/C 语言最为人所诟病的特性之一就是存在内存… 文章目录 前言一、智能指针的发展历史1.C 98/03 的尝试——std::auto_ptr2.std::unique_ptr3.std::shared_ptr4.std::weak_ptr5.智能指针的大小6.智能指针使用注意事项 二、智能指针的模拟实现三、C11和boost中智能指针的关系 前言
C/C 语言最为人所诟病的特性之一就是存在内存泄露问题因此后来的大多数语言都提供了内置内存分配与释放功能有的甚至干脆对语言的使用者屏蔽了内存指针这一概念。这里不置贬褒手动分配内存与手动释放内存有利也有弊自动分配内存和自动释放内存亦如此这是两种不同的设计哲学。有人认为内存如此重要的东西怎么能放心交给用户去管理呢而另外一些人则认为内存如此重要的东西怎么能放心交给系统去管理呢在 C/C 语言中内存泄露的问题一直困扰着广大的开发者因此各类库和工具的一直在努力尝试各种方法去检测和避免内存泄露如 boost智能指针技术应运而生。 一、智能指针的发展历史
1.C 98/03 的尝试——std::auto_ptr
auto_ptr 是c 98定义的智能指针模板其定义了管理指针的对象可以将new 获得直接或间接的地址赋给这种对象。当对象过期时其析构函数将使用delete 来释放内存
class Date {
private:int year_;int month_;int day_;
public:Date(int year 2024, int month 1, int day 1):year_(year), month_(month), day_(day){}
};int main()
{auto_ptrDate d1(new Date(2008, 1, 1));return 0;}通过上面的代码我们发现智能指针实现了自动管理在对象生命周期结束时会自动释放内存不需要程序员手动释放减轻了程序员的负担。
但为什么auto_ptr会被抛弃呢是因为它的实现原理有局限性
拷贝或赋值会改变资源的所有权在STL容器中使用auto_ptr存在着重大风险因为容器内的元素必须支持可复制和可赋值不支持对象数组的内存管理
我们用调试的监视窗口可以看到auto_ptr在赋值和拷贝时是用控制权转移实现的所以它不能用在STL 容器中因为容器内的元素必须支持赋值和拷贝。 另外auto_ptr 不能支持数组对象的管理所以c用更严谨的unique_ptr代替了auto_ptr 2.std::unique_ptr
std::unique_ptr 对其持有的堆内存具有唯一拥有权即引用计数永远是1std::unique_ptr 对象销毁时会释放其持有的堆内存。可以使用以下方式初始化一个 std::unique_ptr 对象
//初始化方式1
std::unique_ptrint sp1(new int(123));//初始化方式2
std::unique_ptrint sp2;
sp2.reset(new int(123));//初始化方式3
std::unique_ptrint sp3 std::make_uniqueint(123);你应该尽量使用初始化方式 3 的方式去创建一个 std::unique_ptr 而不是方式 1 和 2因为形式 3 更安全原因在其《Effective Modern C》中已经解释过了有兴趣的读者可以阅读此书相关章节。 令很多人对 C11 规范不满的地方是C11 新增了 std::make_shared() 方法创建一个 std::shared_ptr 对象却没有提供相应的 std::make_unique() 方法创建一个 std::unique_ptr 对象这个方法直到 C14 才被添加进来。当然在 C11 中你很容易实现出这样一个方法来 templatetypename T, typename... Ts
std::unique_ptrT make_unique(Ts ...params)
{return std::unique_ptrT(new T(std::forwardTs(params)...));
}鉴于 std::auto_ptr 的前车之鉴std::unique_ptr 禁止复制语义为了达到这个效果它的类的拷贝构造函数和赋值运算符被标记为 delete。
template class T
class unique_ptr
{//省略其他代码...//拷贝构造函数和赋值运算符被标记为deleteunique_ptr(const unique_ptr) delete;unique_ptr operator(const unique_ptr) delete;
};因此下列代码是无法通过编译的 禁止复制语义也存在特例即可以通过一个函数返回一个 std::unique_ptr
#include memorystd::unique_ptrint func(int val)
{std::unique_ptrint up(new int(val));return up;
}int main()
{std::unique_ptrint sp1 func(123);return 0;
}上述代码从 func 函数中得到一个 std::unique_ptr 对象然后返回给 sp1。
既然 std::unique_ptr 不能复制那么如何将一个 std::unique_ptr 对象持有的堆内存转移给另外一个呢答案是使用移动构造示例代码如下
#include memoryint main()
{std::unique_ptrint sp1(std::make_uniqueint(123));std::unique_ptrint sp2(std::move(sp1));std::unique_ptrint sp3;sp3 std::move(sp2);return 0;
}以上代码利用 std::move 将 sp1 持有的堆内存值为 123转移给 sp2再把 sp2 转移给 sp3。最后sp1 和 sp2 不再持有堆内存的引用变成一个空的智能指针对象。并不是所有的对象的 move 操作都有意义只有实现了移动构造函数或移动赋值运算符的类才行而 std::unique_ptr 正好实现了这二者以下是实现伪码
templatetypename T, typename Deletor
class unique_ptr
{//其他函数省略...
public:unique_ptr(unique_ptr rhs){this-m_pT rhs.m_pT;//源对象释放rhs.m_pT nullptr;}unique_ptr operator(unique_ptr rhs){this-m_pT rhs.m_pT;//源对象释放rhs.m_pT nullptr;return *this;}private:T* m_pT;
};自定义智能指针对象持有的资源的释放函数
默认情况下智能指针对象在析构时只会释放其持有的堆内存调用 delete 或者 delete[]但是假设这块堆内存代表的对象还对应一种需要回收的资源如操作系统的套接字句柄、文件句柄等我们可以通过自定义智能指针的资源释放函数。假设现在有一个 Socket 类对应着操作系统的套接字句柄在回收时需要关闭该对象我们可以如下自定义智能指针对象的资源析构函数这里以 std::unique_ptr 为例
#include iostream
#include memoryclass Socket
{
public:Socket() {}~Socket() {}//关闭资源句柄void close() {}
};int main()
{auto deletor [](Socket* pSocket) {//关闭句柄pSocket-close();//TODO: 你甚至可以在这里打印一行日志...delete pSocket;};std::unique_ptrSocket, void(*)(Socket * pSocket) spSocket(new Socket(), deletor);return 0;
}自定义 std::unique_ptr 的资源释放函数其规则是
std::unique_ptrT, DeletorFuncPtr其中 T 是你要释放的对象类型DeletorPtr 是一个自定义函数指针。表示 DeletorPtr 有点复杂我们可以使用 decltype(deletor) 让编译器自己推导 deletor 的类型
std::unique_ptrSocket, decltype(deletor) spSocket(new Socket(), deletor);3.std::shared_ptr
std::unique_ptr 对其持有的资源具有独占性而 std::shared_ptr 持有的资源可以在多个 std::shared_ptr 之间共享每多一个 std::shared_ptr 对资源的引用资源引用计数将增加 1每一个指向该资源的 std::shared_ptr 对象析构时资源引用计数减 1最后一个对象析构时发现资源计数为 0将释放其持有的资源。多个线程之间递增和减少资源的引用计数是安全的。注意这不意味着多个线程同时操作 std::shared_ptr 引用的对象是安全的。
std::shared_ptr 提供了一个 use_count() 方法来获取当前持有资源的引用计数。除了上面描述的std::shared_ptr 用法和 std::unique_ptr 基本相同。 实际开发中有时候需要在类中返回包裹当前对象this的一个 std::shared_ptr 对象给外部使用C 新标准考虑到了这一点有如此需求的类只要继承自 std::enable_shared_from_this 模板对象即可。用法如下
class A : public std::enable_shared_from_thisA
{
public:std::shared_ptrA getSelf(){return shared_from_this();}
};int main()
{std::shared_ptrA sp1(new A());std::shared_ptrA sp2 sp1-getSelf();std::cout use count: sp1.use_count() std::endl;return 0;
}上述代码中类 A 的继承 std::enable_shared_from_this 并提供一个 getSelf() 方法返回自身的 std::shared_ptr 对象在 getSelf() 中调用 shared_from_this() 即可。
std::enable_shared_from_this 用起来比较方便但是也存在很多不易察觉的陷阱。
陷阱一不应该共享栈对象的 this 给智能指针对象
int main()
{A a;std::shared_ptrA sp2 a.getSelf()std::cout use count: sp2.use_count() std::endl;return 0;
}运行修改后的代码会发现程序在 std::shared_ptr sp2 a.getSelf(); 产生崩溃。这是因为智能指针管理的是堆对象栈对象会在函数调用结束后自行销毁因此不能通过 shared_from_this() 将该对象交由智能指针对象管理。切记智能指针最初设计的目的就是为了管理堆对象的即那些不会自动释放的资源。
陷阱二避免 std::enable_shared_from_this 的循环引用问题
class A : public std::enable_shared_from_thisA
{
public:A(){m_i 9;//注意://比较好的做法是在构造函数里面调用shared_from_this()给m_SelfPtr赋值//但是很遗憾不能这么做,如果写在构造函数里面程序会直接崩溃std::cout A constructor std::endl;}~A(){m_i 0;std::cout A destructor std::endl;}void func(){m_SelfPtr shared_from_this();}public:int m_i;std::shared_ptrA m_SelfPtr;};int main()
{{std::shared_ptrA spa(new A());spa-func();}return 0;
}运行上面的代码我们发现在程序的整个生命周期内只有 A 类构造函数的调用输出没有 A 类析构函数的调用输出这意味着 new 出来的 A 对象产生了内存泄漏了 我们来分析一下为什么 new 出来的 A 对象得不到释放。spa 出了其作用域准备析构在析构时其发现仍然有另外的一个对象即 A::m_SelfPtr 引用了 A因此 spa 只会将 A 的引用计数递减为 1然后就销毁自身了。现在留下一个矛盾的处境必须销毁 A 才能销毁其成员变量 m_SelfPtr而销毁 m_SelfPtr 必须先销毁 A。这就是所谓的 std::enable_shared_from_this 的循环引用问题。我们在实际开发中应该避免做出这样的逻辑设计这种情形下即使使用了智能指针也会造成内存泄漏。也就是说一个资源的生命周期可以交给一个智能指针对象但是该智能指针的生命周期不可以再交给整个资源来管理。
4.std::weak_ptr
std::weak_ptr 是一个不控制资源生命周期的智能指针是对对象的一种弱引用只是提供了对其管理的资源的一个访问手段引入它的目的为协助 std::shared_ptr 工作。
std::shared_ptr 可以直接赋值给 std::weak_ptr 也可以通过 std::weak_ptr 的 lock() 函数来获得 std::shared_ptr。它的构造和析构不会引起引用计数的增加或减少。std::weak_ptr 可用来解决 std::shared_ptr 相互引用时的死锁问题。 既然std::weak_ptr 不管理对象的生命周期那么其引用的对象可能在某个时刻被销毁了如何得知呢std::weak_ptr 提供了一个 expired() 方法来做这一项检测返回 true说明其引用的资源已经不存在了返回 false说明该资源仍然存在这个时候可以使用 std::weak_ptr 的 lock() 方法得到一个 std::shared_ptr 对象然后继续操作资源以下代码演示了该用法
//tmpConn_ 是一个 std::weak_ptrTcpConnection 对象
//tmpConn_引用的TcpConnection已经销毁直接返回
if (tmpConn_.expired())return;std::shared_ptrTcpConnection conn tmpConn_.lock();
if (conn)
{//对conn进行操作省略...
}既然使用了 std::weak_ptr 的 expired() 方法判断了对象是否存在为什么不直接使用 std::weak_ptr 对象对引用资源进行操作呢实际上这是行不通的std::weak_ptr 类没有重写 operator- 和 operator* 方法因此不能像 std::shared_ptr 或 std::unique_ptr 一样直接操作对象同时 std::weak_ptr 类也没有重写 operator! 操作因此也不能通过 std::weak_ptr 对象直接判断其引用的资源是否存在 std::weak_ptr 的正确使用场景是那些资源如果可能就使用如果不可使用则不用的场景它不参与资源的生命周期管理。例如网络分层结构中Session 对象会话对象利用 Connection 对象连接对象提供的服务工作但是 Session 对象不管理 Connection 对象的生命周期Session 管理 Connection 的生命周期是不合理的因为网络底层出错会导致 Connection 对象被销毁此时 Session 对象如果强行持有 Connection 对象与事实矛盾。
5.智能指针的大小
在64位机器下unique_ptr与普通指针大小一样share_ptr / weak_ptr是普通指针的2倍 6.智能指针使用注意事项
a. 一旦一个对象使用智能指针管理后就不该再使用原始裸指针去操作
b. 分清楚场合应该使用哪种类型的智能指针 通常情况下如果你的资源不需要在其他地方共享那么应该优先使用 std::unique_ptr反之使用 std::shared_ptr当然这是在该智能指针需要管理资源的生命周期的情况下如果不需要管理对象的生命周期请使用 std::weak_ptr。
二、智能指针的模拟实现
我们只实现它最核心的部分
利用 RAII一种利用对象生命周期来控制程序资源的简单技术来管理指针像指针一样使用
auto_ptr:
templateclass T
class auto_ptr {
private:T* _ptr;
public:auto_ptr(T* ptr):_ptr(ptr){}~auto_ptr() { delete _ptr; }auto_ptr(auto_ptrT sp):_ptr(sp._ptr){sp._ptr nullptr;}T operator*() { return *_ptr; }T* operator-() { return _ptr; }
};unique_ptr:
templateclass T
class unique_ptr {
private:T* _ptr;
public:unique_ptr(T* ptr):_ptr(ptr){}~unique_ptr() { delete _ptr; }unique_ptr(const unique_ptrT up) delete;unique_ptrT operator(const unique_ptrT up) delete;T operator*() { return *_ptr; }T* operator-() { return _ptr; }
};shared_ptr:
templateclass T
class shared_ptr {
private:T* _ptr;int* _pcount;std::mutex* _pmtx;public:shared_ptr(T* ptr nullptr):_ptr(ptr),_pcount(new int(1)),_pmtx(new std::mutex){}~shared_ptr() { Release(); }shared_ptr(const shared_ptrT sp):_ptr(sp._ptr),_pcount(sp._pcount),_pmtx(sp._pmtx){{_pmtx-lock();(*_pcount);_pmtx-unlock();}}shared_ptrT operator(const shared_ptrT sp){if (_ptr sp._ptr) return *this;Release();_pcount sp._pcount;_ptr sp._ptr;_pmtx sp._pmtx;{_pmtx-lock();(*_pcount);_pmtx-unlock();}return *this;}void Release(){bool flag false;{_pmtx-lock();if (--(*_pcount) 0){delete _ptr;delete _pcount;flag true;}_pmtx-unlock();}if (flag) delete _pmtx;}int use_count() const { return *_pcount; }T* get() const { return _ptr; }T operator*() { return *_ptr; }T* operator-() { return _ptr; }
};weak_ptr: templateclass Tclass weak_ptr{private:T* _ptr;public:weak_ptr():_ptr(nullptr){}weak_ptr(const weak_ptrT wp):_ptr(wp._ptr){}weak_ptr(const shared_ptrT sp):_ptr(sp.get()){}weak_ptrT operator(const shared_ptrT sp){_ptr sp.get();return *this;}T operator*() { return *_ptr; }T* operator-() { return _ptr; }};
}三、C11和boost中智能指针的关系
C 98 中产生了第一个智能指针auto_ptr.C boost给出了更实用的scoped_ptr和shared_ptr和weak_ptr.C TR1引入了shared_ptr等。不过注意的是TR1并不是标准版。C 11引入了unique_ptr和shared_ptr和weak_ptr。需要注意的是unique_ptr对应boost的scoped_ptr。并且这些智能指针的实现原理是参考boost中的实现的。