【问题标题】:Destructor and Classes in C++ [ Memory Leak]C++ 中的析构函数和类 [内存泄漏]
【发布时间】:2015-08-28 10:08:40
【问题描述】:

我在释放内存时遇到问题。首先我要展示我的构造函数、析构函数和类的私有部分:

第 1 类:

class VectorDinamico {

private:
    int util, tam;
    int *vect;

   /*Rest of the class*/
}

VectorDinamico::VectorDinamico() {
    util = 0;
    tam = 10;
    vect = new int[10];
}

VectorDinamico::VectorDinamico(const VectorDinamico &v){
    vect = new int[v.tam];

    for (int i = 0 ; i < v.util; i++) {
        vect[i] = v.vect[i];
    }
    util = v.util;
    tam = v.tam;
 }

VectorDinamico::~VectorDinamico() {
    delete []vect;
}

VectorDinamico& VectorDinamico::operator= (const VectorDinamico &v) {
    if (this != &v) {
        delete []vect;

        vect = new int[v.tam];

        for (int i = 0 ; i < v.util; i++) {
            vect[i] = v.vect[i];
        }
        util = v.util;
        tam = v.tam;
    }
    return *this;
}

工作正常(Valgrind:退出时没有使用任何东西)

第 2 类:

class Conjunto {
private:
    VectorDinamico datos;

    /*Rest of the class*/
}

//Is empty because it uses the VectorDinamico constructor( I think )
Conjunto::Conjunto() {}

Conjunto::Conjunto( const Conjunto &c) {
   datos = c.datos; // = Is overloaded in VectorDinamico
}

//Is empty because it uses the VectorDinamico destructor( I think )
Conjunto::~Conjunto() {};

Conjunto& Conjunto::operator=( const Conjunto &c) {
    if (this != &c)
        datos = c.datos;

    return *this;
}

工作正常(Valgrind:退出时没有使用任何东西)

第 3 课:(我认为这是问题所在)

class SetConjuntos{
private:
    Conjunto *conjuntos;
    int util, tam;

    /*Rest of the class*/
}

SetConjuntos::SetConjuntos(){
    util = 0;
    tam = 10;
    conjuntos = new Conjunto[10];
}

SetConjuntos::SetConjuntos(const SetConjuntos &sc){
    util = sc.util;
    tam = sc.tam;
    conjuntos = new Conjunto[tam];

    for(int i = 0 ; i < util ; i++)
    conjuntos[i]=sc.conjuntos[i];
}
//Here is my problem ( I think )
//anyway I've left it empty, because I think it uses the Conjunto destructor's
SetConjuntos::~SetConjuntos(){
    //delete []conjuntos; ( Cause segmetation fault )
}

我认为 SetConjuntos 析构函数不正确,因为 Valgrind 输出是:

==6838== LEAK SUMMARY:
==6838==    definitely lost: 2,912 bytes in 4 blocks
==6838==    indirectly lost: 11,320 bytes in 180 blocks

SetConjuntos 的析构函数必须是怎样的?

谢谢

------------解决了将 operator= 添加到 SetConjuntos----------------

我没有实现赋值运算符,因为他认为如果他不显式使用是没有必要的。我错了。

现在第 3 课是:

class SetConjuntos{
private:
    Conjunto *conjuntos;
    int util, tam;

    /*Rest of the class*/
}

SetConjuntos::SetConjuntos(){
    util = 0;
    tam = 10;
    conjuntos = new Conjunto[10];
}

SetConjuntos::SetConjuntos(const SetConjuntos &sc){
    util = sc.util;
    tam = sc.tam;
    conjuntos = new Conjunto[tam];

    for(int i = 0 ; i < util ; i++)
    conjuntos[i]=sc.conjuntos[i];
}

SetConjuntos::~SetConjuntos(){
    delete []conjuntos; //(  NO cause segmetation fault )
}

SetConjuntos& SetConjuntos::operator=(const SetConjuntos &sc){
    if(this != &sc){
        util = sc.util;
        tam = sc.tam;
        Conjunto *conjuntos_loc = new Conjunto[tam];

        for(int i = 0 ; i < util ; i++)
            conjuntos_loc[i]=sc.conjuntos[i];

        delete[] conjuntos;
        conjuntos = conjuntos_loc;
    }
    return *this;
}

【问题讨论】:

  • 你有复制构造函数吗?
  • “它使用 xxx 构造函数/析构函数”不好说。它调用所有数据成员的构造函数(分别是析构函数)(如果它们有,我的意思是——如果它们不是原始类型)。 VectorDinamico 的构造函数和析构函数都可以。 Conjunto 也是。但是您必须删除SetConjuntos 中分配的(通过new)数据。请参阅@Barry 的评论。
  • 顺便说一句,我尊重你使用valgrind - 没有多少初学者使用它。甚至更多 - 很多专业人士不使用它..
  • 如果SetConjuntos 的析构函数中的delete[] 导致分段错误,则说明您在其他地方有问题 - 它应该在那里。不过,您应该在该类中有一个赋值运算符。
  • 附带说明,您可能希望使用初始化列表而不是在构造函数的主体中初始化 datos 类中的 datos 成员。就像现在一样,您的程序调用datos 的默认构造函数,然后调用operator= 运算符。最好只使用初始化列表中的复制构造函数进行初始化。一般来说,最好尽可能使用初始化列表。在您的情况下,您可以将它们用于所有构造函数中的所有数据成员。

标签: c++ class memory-leaks valgrind destructor


【解决方案1】:

显示的代码没有明显的错误,因此错误在于您没有在问题中提出。

作为一个疯狂的猜测,你(可能不由自主地)使用复制构造或分配,这会产生所有权问题。

记住规则:要么你没有析构函数、赋值运算符或复制构造函数,要么你可能需要这三个。

这条规则非常重要,如果您碰巧遇到不需要复制构造函数但需要析构函数(想不出,但它可能存在)的情况,请用评论说这不是你忘记的事情。

【讨论】:

  • 我忘了放复制构造函数,抱歉(现在是)
  • 没有复制构造函数但有析构函数的明显情况是std::unique_ptr。也可能是std::auto_ptr,但我记得的方式是复制是疯狂的并且公然破坏,而不是非法的。显然,这两者有一些非常相似的地方,而且他们发现了这个要求是多么不寻常。
【解决方案2】:

我只是想向您指出,有更好的方法来编写复制分配运算符。让我们看一下您的代码:

VectorDinamico& VectorDinamico::operator= (const VectorDinamico &v) {
    if (this != &v) {
        delete []vect;
            /* What happens here is that you delete vect [ ], but what
               happens if the next line 'vect = next int [ v.tam ];' throws
               an exception?
            */
        vect = new int[v.tam];

        for (int i = 0 ; i < v.util; i++) {
            vect[i] = v.vect[i];
        }
        util = v.util;
        tam = v.tam;
    }
    return *this;
}

看看The rule of three,这里会详细解释如何编写一个复制赋值操作符。

【讨论】:

  • 哇,我从来没有想过这个。感谢您的建议。
  • @BufferOverflow 这应该是评论而不是答案,因为它绝不会回答问题。
【解决方案3】:

您似乎正在学习 15 年前的 C++。

  1. 使用std::vector&lt;int&gt; 而不是自己编写VectorDinamico
    • 这已经有正确的副本、分配等。
  2. 如果您做不到1,请使用std::unique_ptr&lt;int[]&gt; 而不是原始的int *vect。它完全省去了您编写析构函数的麻烦。不过,您仍然需要编写副本和副本分配。

    • 编写复制赋值的规范正确方法是首先编写swap 方法或重载,如下所示:

      void VectorDinamico::swap(VectorDinamico &other) {
          std::swap(util, other.util);
          std::swap(tam,  other.tam);
          std::swap(vect, other.vect);
      }
      

      然后编写复制赋值运算符,这样它就可以重用复制构造函数、交换和析构函数:

      VectorDinamico& VectorDinamico::operator=(VectorDinamico const &other) {
          VectorDinamico tmp(other);
          tmp.swap(*this);
      }
      

      除了更简单且重复更少的代码之外,好处是如果分配失败并抛出bad_alloc,两个原始向量都处于明确定义的状态(您的原始代码使左侧的向量损坏)

  3. 使用std::unique_ptr 来包装其他两个类的原始指针成员,并停止手动编写析构函数

  4. 如果您正在编写复制构造函数和复制赋值运算符,您可能应该考虑编写移动版本

  5. 如果您正在运行 valgrind 并且它发现了泄漏,它会告诉您该内存被分配到哪里。只要确保你的构建中有调试符号

  6. 如果您不确定您的析构函数是否被调用,只需添加一条打印语句并查看,或在调试器中单步执行。看到 I think this is called here 这样的 cmets 只是告诉我你还没有尝试找出答案。

  7. 最后是你眼前的问题:

    //Here is my problem ( I think )
    //anyway I've left it empty, because I think it uses the Conjunto destructor's
    SetConjuntos::~SetConjuntos(){
        //delete []conjuntos; ( Cause segmetation fault )
    }
    

    您的 conjuntos 是一个原始指针 - 您已在其他任何地方手动删除它们,这里有什么不同?您应该取消注释此删除。

    如果您在未注释的情况下遇到分段错误,请在 gdb 下运行它或获取核心转储并查看位置。另请查看 valgrind 或 glibc 是否警告您有关双重释放。

    几乎可以肯定,您要删除的数组成员之一已损坏,而找到损坏的根源是您真正的问题。

【讨论】:

    【解决方案4】:

    SetConjutos 的析构函数就像你拥有它一样好:

    SetConjuntos::~SetConjuntos(){
        delete [] conjuntos;
    }
    

    但是,如果您有指针成员(在 VectorDinamico 和 SetConjutos 中),您需要定义一个复制构造函数和赋值运算符:

    VectorDinamico::VectorDinamico(const VectorDinamico &v) :
        util(v.util),
        tam(v.tam),
        vect(new int[10])
    {
        //copy Content of v.vect to vect
    }
    
    VectorDinamico& Operator=(const VectorDinamico &v)
    {
        //Copy util, tam and the CONTENT of vec
        return *this;
    }
    

    注意:您还需要为 SetConjuntos 定义一个复制构造函数和赋值运算符。

    当您没有复制构造函数和赋值运算符时,您可能会尝试多次释放内存。

    正如您在我的复制构造函数中看到的那样,我正在使用一个我也推荐使用的初始化列表。

    【讨论】:

    • 我忘了放复制构造函数,抱歉(现在是)
    猜你喜欢
    • 1970-01-01
    • 2012-09-02
    • 2013-11-28
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2021-06-13
    • 1970-01-01
    相关资源
    最近更新 更多