使用全局指针和自定义析构函数 - 如何避免内存泄漏?

时间:2015-03-11 14:02:28

标签: c++ qt memory-leaks destructor

我一直在用Bjarne Stroustrup的书[C ++ 11]为我的论文学习C ++。即便如此,我仍然没有得到关于在C ++中处理内存泄漏的事情。他在书中说了很多,以避免使用newdelete以及自定义析构函数。

我想说我正在使用QT来构建我的UI,现在请考虑这个例子来解释我的前三个问题:

    class KHUB : public QMainWindow
{
    Q_OBJECT

public:

    KHUB(QWidget *parent = 0)
    {
        createGroupScreen();
    };

    ~KHUB();

private:

        QWidget *groupScreen;
        void createGroupScreen()
        {
            groupScreen = new QWidget();
        };
}

这是我的应用程序的恢复。我有一个名为KHUB的构造函数,它创建一个对话框(QWidget),提供一些选项来放置名称,类别等,以创建一个新组。

  1. 因为我应该避免裸newdelete,是否有不同的方式(例如使用unique_ptr,不确定这是否适用于此概念)或不是错误容易创建这个QWidget并且还有对象引用来在我的createGroupScreen成员函数之外操作它?

  2. 如果我在成员函数中创建了这个对象,没有全局指针,也没有使用裸new,只要它的父级是活着的或者在结尾处,该对象将保留在内存中会员功能会被删除吗?

  3. 如果它的父节点仍然存在,我如何获取成员函数之外的对象引用,以便我可以调用~QWidget()

  4. 我的第四个问题是关于同样的物体破坏。

    1. 如果我通过调用groupScreen来销毁groupScreen->~QWidget(),这只会破坏我的QWidget对象,我的指针将保留在那里作为内存泄漏等待我delete groupScreen
    2. 考虑我的第五,第六和最后一个问题的另一段代码:

              class KHUB : public QMainWindow
      {
          Q_OBJECT
      
      public:
      
          KHUB(QWidget *parent = 0)
      
          ~KHUB()
          {
              delete groupScreen;
              delete buttonOne;
              delete buttonTwo;
              delete buttonThree;
          };
      
      private:
      
              QWidget *groupScreen;
      
              QPushButton *buttonOne, *buttonTwo, *buttonThree;
      
              /* Components Setup */
              void btSetup(QPushButton** button, const QString name, int posX, int posY, int width, int height);
      }
      

      我正在尝试使用具有设置按钮名称,位置和大小的功能的按钮进行一些代码重用,因此通过对每个按钮重复键入相同的参数,代码不会变得如此之大 - 但是我我也对节目表现感到担忧。

      1. 当我调用我的KHUB构造函数时,这并不意味着我的.h文件中的所有全局指针都被正确分配了吗?只有在初始化时才会发生这种情况,对吗?

      2. 如果我真的需要创建自定义析构函数,我应该在其中删除我的.h文件中声明的每个全局指针?我想知道如果我没有通过其中一个指针的初始化,这不会有风险。

      3. 有没有办法循环指针获取和删除?即for(auto p : globalPtrs) delete p;

      4. 我试图在堆栈中找到关于这个主题的一些问题,但没有一个对我很有启发性:

        1. C++ destructor memory leak
        2. Avoiding memory leak
        3. Avoiding memory leaks

1 个答案:

答案 0 :(得分:2)

您可能应该首先阅读memory management

上的C ++常见问题解答
  

因为我应该避免裸体和删除,所以有不同的方式(例如使用unique_ptr,不确定它是否是一个&#39   这里适用的概念)或不那么容易创建这个QWidget   并且仍然有对象引用来操纵它在我之外   createGroupScreen成员函数?

是的,如果是现代C ++,您应该使用std::shared_ptrstd::unique_ptrstd::weak_ptr。所有这些都照顾到他们所指的记忆的破坏。他们每个人在所有权,分享等方面都有不同的特点。

我尝试使用它们,因为它们简化了内存管理。如果不可能(例如你的编译器不符合C ++ 11并且没有使用boost ......可能有很多原因),你可以使用new / delete ...只是要格外小心。

  

如果我在成员函数中创建此对象,而不是全局   指针也没有使用裸新,这个对象将保留在   记忆,只要它的父母是活着的或在成员的最后   功能会被删除吗?

不确定你的意思。 如果你正在创建一个变量,方法/函数本地,对象属性......等等。当它定义的范围消失时,该变量将被销毁。

说你有:

void myfunc() {
   int a=0;
}

当您输入范围时,将创建整数a的内存,当您离开范围时,将销毁整数a的内存。在这种情况下,它将是堆栈上的内存(相对于堆上的内存)

如果类中有属性,则在创建对象时,将创建该属性(构造函数)。当对象被销毁时,该属性将被销毁(析构函数)。

当您使用new分配时,将从堆中分配内存。如果在(普通)指针变量中存储对该内存的引用,则当指针变量被销毁时,指针引用的内存不会被销毁(并且您有泄漏)。

当你有一个指针引用的内存并且为该指针分配不同的内存引用时,可以说同样的事情。如果内存未在其他地方引用,则程序将丢失内存。

  

如果它与其父级一起存活,我该如何获取该对象   在成员函数之外引用所以我可以调用~QWidget()?

如果你使用new分配,那么你必须在超出范围时销毁它,或者将内存引用保存到某个其他范围内的指针变量,你可以在什么时候删除它。

请参阅C ++常见问题解答:Why isn’t the destructor called at the end of scope?

您可能想要阅读的内容是RAII,因为我在第一个问题中列出的智能指针使用此习惯用法。

  

如果我通过调用groupScreen-> ~QWidget()来销毁groupScreen,这将会   只破坏我的QWidget对象,我的指针将保留在那里作为a   内存泄漏等我删除groupScreen?

请参阅C ++常见问题解答:What are the two steps that happen when I say delete p?

调用delete groupScreen将导致执行~QWidget()并且所有属性(非指针/引用)的析构函数和父类的析构函数(如果有)以及释放对象的内存。

当您处理类的层次结构时,重要的是要考虑是否可以通过指向父类的指针删除该对象。如果是这种情况,则必须将析构函数方法声明为virtual,以便执行实际的对象类方法(而不是执行指针指向的类的析构函数)(这是另一种形式的泄漏)。

  

当我调用我的KHUB构造函数时,这并不意味着所有的全局指针   我在我的.h文件中的分配正确吗?那只会发生   什么时候初始化,对吗?

当您调用构造函数时,在执行自定义构造函数之前,将分配属性。但是指针不会被分配给它分配的内存,只是指针本身的内存(重要的是你要把它初始化为一些合理的值,例如null_ptr)

在类的外部或类的静态属性中声明的变量,不应在* .h中初始化,因为它们会导致符号出现多次(因为标题包含在不同的编译单位)

在* .cpp中初始化它们。但要小心,因为它可能是问题的原因(见C++ FAQ

  

如果我真的需要创建自定义析构函数,我应该删除它   我的.h文件中声明的每个全局指针?我想知道是不是   如果我没有通过初始化,那将不会有风险   其中一个指针。

如果

,您应该创建自定义析构函数
  • 您想对对象销毁做任何事情
  • 如果您的对象包含它拥有的动态内存(裸)指针(即不会由其他人处理)
  

有没有办法循环指针获取和删除?即   for(auto p:globalPtrs)delete p;

不完全确定你的意思是globalPtrs ...如果它是一个包含对象中所有指针的神奇变量,那就不是这样了。

你必须注意删除每个指针(你拥有)......这就是智能指针不易出错的原因,因为它们会自动执行。


也许一个例子会有所帮助:

#include <iostream>

class B {
public:
  B() {
    std::cout << "B()" << std::endl;
  }

  ~B() {
    std::cout << "~B()" << std::endl;    
  }
};

class A {
public:
  A() {
    std::cout << "A()" << std::endl;
  }

  ~A() {
    std::cout << "~A()" << std::endl;    
  }
  B b;
};

int main() {
  std::cout << "A as variable" << std::endl;
  A a;

  std::cout << "A as pointer" << std::endl;
  A *a_ptr=0;

  std::cout << "running new" << std::endl;
  a_ptr=new A();

  std::cout << "running delete" << std::endl;
  delete a_ptr;    

}

这会产生以下输出(带有一些注释)

A as variable     // We are declaring a stack variable of type A
B()               // since A contains a B attribute object it creates it 
A()               // ... and runs the constructor
A as pointer      // We are declaring a stack variable pointer to A
                  // ... nothing happens
running new       // but when we run new, we get
B()               // the creation of the attribute (and its constructor)
A()               // the execution of the constructor
running delete    // now we call delete
~A()              // that calls A's destructor
~B()              // and then proceeds to destroy the attributes.
~A()              // these are the destructor for the first variable
~B()              // which is now going out of scope

让我们尝试不同的东西吧。相同的类定义。我们的主要是现在:

int main() { 
  std::cout << "A as pointer" << std::endl;
  A *a_ptr=0;

  std::cout << "running new" << std::endl;
  a_ptr=new A();

}

我们得到了

A as pointer
running new
B()
A()

没有析构函数,我们已经泄露了A对象。