天天看点

Qt平台下C++内存管理 2021-08-10

01 编程碎念我的编程观念,是在公司砖海中,受导师和大咖影响逐步形成。文中观点均来源于真实的技术实践,部分理念也是继承前人的设计成果,如有侵犯的地方请告知。在此特别感谢给予我点拨的人,站在大咖的肩膀上,让我们看得更远。文中提到的编程观念,大部分是基于C++实现的基础技术,有Qt框架相关的,也有设计模式、面向对象相关的。可能大家会想,现在都是AI云的时代了,这些太老了。但我认为,这些才是编程的基础,这就是正确的编程世界观。

02 内存管理某日,我在调试bug的时候,发现Qt源码中已经少有“”形式的指针了。回想公司的代码,大多写于2010年,满满的星,我闭了一下眼睛,感觉到熟悉的腐败味道。2019年接手一个项目,一个反复无常近乎夭折的项目。在此之前,已经有同事在里面挣扎了几月,眼看要发布版本了,压力测试却不能运行3时。C++编程中,内存一直就是显著的问题。在Qt平台中,我推荐使用Qt智能指针、对象树、大内存技术来进行内存管理。

03智能指针范围指针QScopedPointerQScopedPointer p(new MyClass ());Scope为“范围”的意思。可以理解为,QScopedPointer对象p管理的内存,在p生命周期内有效。这是一个在项目中经常使用的智能指针,经常使用的场景:1、类的成员指针

class MyPrivateClass; 
class MyClass
{
  private:
      QScopedPointer<MyPrivateClass> privatePtr; 
  public:
      MyClass(); 
      inline ~MyClass() {} 
};


           

注意事项:QScopedPointer禁止与QObject对象树中的对象组合使用,对象树中parent释放的时候,会释放其子节点,QScopedPointer对象再次释放child内存时,会导致错误。

对象追踪指针QPointerQPointer label = new QLabel(parent);针对QObject的追踪指针,QPointer并不会释放目标对象,用意在于记录QObject,以便在其他成员函数中使用。

QPointer就像一个GPS定位器,在目标对象被释放后,自动置为0,即parent析构时析构子对象QLabel,此后label记录的内存为0。

需要注意管理的目标对象必须从QObject派生。这是一个在项目中经常使用的智能指针,经常使用的场景:

1、 QObject子对象,作为成员指针时

2、 记录非自身管理的QObject对象指针时共享指针QSharedPointer和QWeakPointerQSharedPointer p(new MyClass ());共享指针,意为多个QSharedPointer共享同个内存对象。

当p自身被释放时,会判断自身管理的内存对象,是否有被其他的QSharedPointer引用共享,如果没有则释放管理的内存对象。经常使用的场景:

1、类的成员指针,特别是容器类型中使用,减少了对容器内存对象的释放操作,容器的增删操作,QSharedPointer保证其管理的内存被释放。注意事项:将QSharedPointer管理的对象指针A,注册到其他需要管理A生命周期的类中,会发生错误。

比如postEvent的QEvent对象使用QSharedPointer管理的话,QSharedPointer退出作用域时会释放QEvent,导致postEvent处理过程中对QEvent的访问,或处理完事件后对QEvent的释放,都是错误的。

弱共享指针QWeakPointer刚刚接触Qt的同学,在网上搜索文章时,对弱共享可能一知半解。

QWeekPointer可以理解为与QPointer一样的追踪指针,QPointer目标是记录和追踪QObject对象,QWeekPointer则追踪的是QSharedPointer对象。我在项目中通常很少使用弱共享指针,它的目的是作为QSharedPointer助手类,解除QSharedPointer的循环依赖,在面向对象设计中,出现循环依赖本身就不是一件理所当然的事。

关于智能指针的一些思考智能指针是内存管理的工具,在语法层次上,为C++工程师降低了内存管理难度,能有效预防不经意笔误导致的内存泄漏,并减少了内存管理代码量。

但是,智能指针本质上并不能解决项目中内存管理问题。作为C++工程师,必须理解内存管理机制,明确的知道内存该何时创建、何时销毁。

否则在选择哪种智能指针,用于管理目标内存时,不能做出正确的决定,还是会导致项目内存异常。除非未来Qt的某个版本,只有一个智能指针QPointer。

对于内存问题的解决,实际项目中我们更依靠QObject对象树和内存池。

04 对象树

Qt中用树形结构描述对象之间的父子关系,如下图,P为树的父节点,A2为一级子节点,B1、B2为二级子节点。

Qt平台下C++内存管理 2021-08-10

对象树内存的管理,当A2释放时,先释放B1、B2,再将自身从P的子节点列表中移除。Qt对象树的特征,确保在父对象释放时,释放子对象内存,在实际项目中,我会经常使用。QObject对象树的实现很容易理解,QObjectData中记录指向父对象的指针parent,和一个QList<QObject*>子对象列表children。在UML中QObject描述为自己组合自己,意外不意外。

Qt平台下C++内存管理 2021-08-10

QObject延迟释放deleteLater在QObject对象机制中,有一个非常好用的特性,deleteLater,其作用是延迟对象的析构。什么意思呢,调用deleteLater后,QObject对象不会马上析构,而是在下一个消息循环析构。使用的场景,比如容器中子控件的替换,可以避免画面闪烁,或者是其他一些需要延迟释放的地方。

05 内存池

内存池大内存通常也叫内存池,其实内存池中管理的不仅仅是大内存,还有小内存。在项目中,出于2种目的使用内存池,一是性能提升,二是避免内存碎片。

性能提升

对于频繁申请-释放小块内存的情况,使用内存池比new有显著的性能提升,但仅仅出于此目的,并没有充足的理由用到内存池。

避免内存碎片

对于内存池技术,更多的需要是避免内存碎片。我在项目中曾经遇到,申请20M内存失败的情况,而此时系统内存明显大于20M,为什么呢,因为系统内存被碎片切割了,没有一块连续的20M内存。

Qt平台下C++内存管理 2021-08-10

在系统内存管理不够“聪明”时,C++程序员会有强烈的掌控欲望,希望一切都掌控在自己手中,包括内存的潜在风险。

通过内存池技术,在碎片切割导致内存不连续时,我们的程序有机会重新整理内存,满足申请大小。

Qt平台下C++内存管理 2021-08-10

关于内存池的技术,Qt并没有提供实现,但有很多开源的库参考,可以搜索“内存池”。

Qt

继续阅读