【问题标题】:Using global pointers and custom destructor - How to avoid memory leak?使用全局指针和自定义析构函数 - 如何避免内存泄漏?
【发布时间】:2015-05-13 08:44:46
【问题描述】:

我一直在用 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. 1234563函数会被删除吗?
  3. 如果它在其父级中仍然存在,我如何在成员函数之外获取对象引用以便调用~QWidget()

我的第四个问题是关于同一个对象的破坏。

  1. 如果我通过调用groupScreen->~QWidget() 来破坏groupScreen,这只会破坏我的QWidget 对象,并且我的指针将作为内存泄漏留在那里等待我去delete groupScreen

考虑我的第五个、第六个和最后一个问题的另一段代码:

        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;

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

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

【问题讨论】:

  • 我对内存泄漏的理解(我相信其他人会纠正我或有更好的信息)是你正在使用指针类扩展的任何对象。它们存在于内存或内存中早已存在它在范围内。内存泄漏来自范围内不必要的东西,它们都占用了内存空间。如果你不能使用删除,也许你可以将你正在使用的东西设置为 NULL。您正在使用的操作系统会神奇地处理新对象的内存分配。智能指针是一件事,顺便说一句,这对他们来说是一个很棒的心房umich.edu/~eecs381/handouts/C++11_smart_ptrs.pdf
  • 您应该尽可能使用std::make_sharedstd::make_unique,而不是将new 的结果传递给智能指针构造函数。这就是不直接使用new的意思。
  • 最后,直接显式调用析构函数是相当罕见的。我刚刚浏览了我目前正在处理的一个 50k LOC 项目,并且在任何地方都没有一个显式的析构函数调用。你需要有一个很好的理由这样做,几乎。如果您不编写手动资源管理库代码,则可能不应该显式调用析构函数。
  • 你也忘记了 QObject 是一个智能指针集合(真的!)。 QObject 类似于 std::vector<std::unique_ptr<QObject>>QObject 是一个智能指针,拥有其子项的唯一所有权。对拥有的QObjects 应用额外的内存管理层通常会适得其反。如果他们有父母,父母会担心他们。
  • 最后,在父窗口小部件中存储指向子窗口小部件的唯一指针没有多大意义。孩子可以是简单的直接成员,而不是指针成员。

标签: c++ qt memory-leaks destructor


【解决方案1】:

您可能应该先阅读memory management 上的 C++ 常见问题解答

既然我应该避免裸new和delete,有没有不同的方法(比如使用unique_ptr,不确定是不是 此处适用的概念)或不太容易创建此 QWidget 并且仍然有对象引用来在 my 之外对其进行操作 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 对象,我的指针将作为 内存泄漏等我删除 groupScreen?

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

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

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

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

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

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

在 *.cpp 中初始化它们。但是要小心,因为这可能会导致问题(请参阅C++ FAQ

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

如果

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

有没有办法循环指针获取和删除? IE。 for(auto p : globalPtrs) 删除 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 对象。

【讨论】:

  • "调用delete groupScreen会导致~QWidget()被执行,所有属性的析构函数(非指针/引用)和父类的析构函数(如果有的话)和内存对于已释放的对象。”你确定吗?我认为这会使我失去对 QWidget 的引用并导致内存泄漏,我认为我应该先销毁对象然后删除指针。 - 感谢您的帮助,但我会等着看其他人是否回答我,您的一些回答并没有真正解决我的疑问,但它有所帮助
  • @VictorOliveira。我添加了一些示例代码。您可以使用相同的技术来测试您的假设:添加跟踪以查看何时执行。
  • 我用 vld.h(内存泄漏管理器)检查了你的删除信息,你是对的
  • 我必须问,如果我要删除我的指针,这意味着它分配的空间将不再存在于内存中,还是我只是释放他指向的任何引用?
  • 这个答案只是答案的一半。这是准确的,但它完全忽略了正在谈论的QObjects。
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2017-10-31
  • 2013-09-23
  • 2010-12-22
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多