【问题标题】:A bunch of unclear things with the destructor in C++C ++中的析构函数一堆不清楚的东西
【发布时间】:2012-04-06 13:37:39
【问题描述】:

我用 C++ 编写了一些非常简单的代码来对向量进行一些简单的操作。这是文件vector.h的内容:

#ifndef VECTOR_H_INCLUDED
#define VECTOR_H_INCLUDED

class Vector {
    int *coordinates;
    int *size;
public:
    Vector(int vector_size);
    Vector(int*,int);
    ~Vector();
    void print(void);
    Vector operator +(Vector);
};
#endif

这是实现(文件:vector.cpp):

#include "vector.h"
#include <iostream>
using namespace std;

Vector::Vector(int vector_size) {
    coordinates = new int[vector_size];
    size = new int;
    *size = vector_size;
}

Vector::Vector(int* vector_coordinates, int vector_size){
    coordinates = vector_coordinates;
    size = new int;
    *size = vector_size;
}

void Vector::print(void){
    cout << "[";
    for (unsigned short int index =0; index<*size; index++){
        cout << coordinates[index];
        if (index < *size-1){cout << ", ";};
    }
    cout << "]\n";
}

Vector Vector::operator+ (Vector other) {
  Vector temp(*(other.size));
  if ((*temp.size)!=(*(this->size))){
      throw 100;
  }
  int* temp_c = new int[*(other.size)];
  int* other_c = other.coordinates;
  for (unsigned short int index =0; index<*size; index++){
    temp_c[index] = coordinates[index] + other_c[index];
  }
  temp.coordinates = temp_c;
  return (temp);
}

Vector::~Vector(){
    delete[] coordinates;
    delete size;
}

从我的 main.cpp 中,我执行以下操作:

#include <iostream>
using namespace std;
#include "vector/vector.h"

const int size = 3;

int main() {
    int *xxx = new int[size];
    xxx[0]=4; xxx[1]=5; xxx[2]=-6;


    Vector v(xxx,size);// v = [4, 5, -6]
    Vector w(size);// w is a vector of size 3

    w = v+v; // w should be w=[8,10,-12]
    w.print();
    return 0;
}

那么结果是:

[148836464, 5, -6, 17, 148836384, 0, 0, 17, 0, 0, 0, 17, 3, 0, 0, 17, 0, 0, 0, 17, 148836480, 0, 0 , 17, 0, 10, -12, 135025, 0, 0, 0, 0, 0, 0, , 0, 0,分段错误

如果我从析构函数中删除这两行:

delete[] coordinates;
delete size;

一切正常,程序输出:

[8, 10, -12]

我会很感激任何解释...

更新1:我将operator+方法改成如下,但问题没有解决:

Vector Vector::operator+(Vector other) {
    int size_of_other = *(other.size);
    int size_of_me = *(this->size);
    if (size_of_other != size_of_me) {
        throw 100;
    }
    int* temp_c = new int[size_of_me];
    int* other_c = other.coordinates;
    for (unsigned short int index = 0; index < size_of_me; index++) {
        temp_c[index] = coordinates[index] + other_c[index];
    }
    Vector temp(temp_c,size_of_me);
    return (temp);
}

更新 2: 我注意到使用运算符:

Vector Vector::operator+(Vector other);

我不会得到想要的结果。使它起作用的修改是:

const Vector& Vector::operator+(const Vector& other) {
    Vector temp(other.size);
    for (unsigned short int index = 0; index < size; index++) {
        cout << "("<< index <<") "<<coordinates[index] << "+"
                        <<other.coordinates[index] << ", "<< endl;
        temp.coordinates[index] = coordinates[index] + other.coordinates[index];
    }
    return (temp);
}

更新 3: 在更新 #2 之后,我收到了来自编译器的警告,我返回了本地“临时”。我将我的代码更改为以下,它完全解决了所有问题并且工作正常(我返回一个 copy 的 temp):

const Vector Vector::operator+(const Vector& other) const{
    Vector temp(other.size);
    for (unsigned short int index = 0; index < size; index++) {
        temp.coordinates[index] = coordinates[index] + other.coordinates[index];
    }
    return *(new Vector(temp));
}

【问题讨论】:

  • delete [] coordinates;
  • @JamesMcLaughlin 我更正了。但是,再一次……同样的!我会在我的问题中更新它。
  • 成员size 没有理由成为指针。指针会让你的工作更难。
  • 使用std::unique_ptr 会突出显示您的几个错误。
  • 刚刚发布了一个完整的工作实现,包括我已经验证的复制构造函数和赋值运算符。看看我们一直在建议什么...

标签: c++ pointers destructor


【解决方案1】:

您的Vector::operator+ 至少有一个错误:

int* temp_c = new int;
...
    temp_c[index] =

您正在索引temp_c,而它只分配了一个整数。因此,您的循环正在踩踏其他一些内存,导致未定义的行为。

您还需要定义一个复制构造函数,以便您可以正确使用您的Vector 对象。编译器会生成一个默认的拷贝构造函数,但是默认的一般不适合包含指针的对象。

这一行:

temp.coordinates = temp_c;

导致内存泄漏,因为它会覆盖先前分配的 temp.coordinates 向量。

更新 3:您的代码

return *(new Vector(temp));

虽然它看起来有效,但仍然是内存泄漏。您正在分配一个新的Vector,然后编译器调用复制构造函数将其复制到函数的返回值中。没有人 deletes 你刚刚创建的 Vector 对象,所以存在内存泄漏。

解决方案是编写一个拷贝构造函数,而不是依赖编译器生成的默认拷贝构造函数。您问题的所有其他答案都说了同样的话。您必须为正确的程序执行此操作。

【讨论】:

  • 你是对的!我会更新我的问题。不幸的是,它不能解决问题。
  • @PantelisSopasakis:定义一个复制构造函数怎么样?这有帮助吗?
  • 我可以举一个例子来说明如何在这种情况下使用复制构造函数。我相信我所做的(请参阅我的问题中的更新 1)应该等同于复制构造函数。不是吗?
  • 不,您实际上需要一个真正的复制构造函数。复制构造函数需要Vector(const Vector &amp;) 的签名,并将根据其参数内容的副本 构造一个新的Vector。您还需要定义一个 assignment operator 来完成更多工作。
  • +1 用于复制构造函数和指针。默认的复制构造函数只会复制到浅拷贝并且不理解堆上的内容......
【解决方案2】:

您的类需要复制构造函数和复制赋值运算符才能正常工作。需要它们的一个重要提示是析构函数不是{}。请参阅“Rule of Three”。

为了更好更现代,您还可以考虑使用移动构造函数和移动赋值运算符。

【讨论】:

    【解决方案3】:

    试试下面的代码:

    1. 实现默认构造函数。这表明无论您的对象是如何构造的,您的内部变量都将指向堆上的某个对象或NULL,因此任何delete [] 调用都不会可怕地死掉。
    2. 实现复制构造函数。默认复制构造函数不会在堆上复制内存,所以这对您来说将是一个严重的问题。
    3. 实现赋值运算符。这再次避免了浅拷贝。
    4. 将大小作为指针移除;在大多数系统上,指针的大小与整数相同,因此将大小设为指针只会使事情变得不必要地复杂。
    5. 通过避免中间分配来修复加法构造函数。您在那里有一个临时局部变量,因此请利用它而不是分配几个额外的中间对象。

    ...看看:

    // VectorImplementation.cpp : Defines the entry point for the console application.
    //
    
    #include <iostream>
    using namespace std;
    
    
    class Vector {
        int *coordinates;
        int size;
    
    public:
        Vector();
        Vector(int vector_size);
        Vector(int*,int);
        Vector(const Vector& v);
    
        ~Vector();
    
        Vector operator +(Vector);
        Vector& operator =(const Vector & other);
    
        void print(void);
    };
    
    Vector::Vector() {
        coordinates = NULL;
        size = NULL;
    }
    
    Vector::Vector(int vector_size) {
        coordinates = new int[vector_size];
        size = vector_size;
    }
    
    Vector::Vector(int* vector_coordinates, int vector_size){
        coordinates = vector_coordinates;
        size = vector_size;
    }
    
    Vector::Vector(const Vector& v) {
        size = v.size;
        coordinates = new int[size];
        memcpy(coordinates,v.coordinates, sizeof(int)*size);
    }
    
    void Vector::print(void){
        cout << "[";
        for (unsigned short int index =0; index<size; index++){
            cout << coordinates[index];
            if (index < size-1){cout << ", ";};
        }
        cout << "]\n";
    }
    
    Vector Vector::operator+ (Vector other) {
      Vector temp(other.size);
      for (unsigned short int index =0; index<size; index++){
          temp.coordinates[index] = coordinates[index] + other.coordinates[index];
      }
      return (temp);
    }
    
    Vector & Vector::operator= (const Vector & other)
    {
      if (this != &other) // protect against invalid self-assignment
      {
        // 1: allocate new memory and copy the elements
        int * tmp_coordinates = new int[other.size];
        memcpy(tmp_coordinates, other.coordinates, sizeof(int)*other.size);
    
        // 2: deallocate old memory
        delete [] coordinates;
    
        // 3: assign the new memory to the object
        coordinates = tmp_coordinates;
        size = other.size;
      }
      // by convention, always return *this
      return *this;
    }
    
    Vector::~Vector(){
        printf("Destructing %p\n", this);
        delete[] coordinates;
    }
    
    const int size = 3;
    
    int _tmain(int argc, _TCHAR* argv[])
    {
    
        int *xxx = new int[size];
        xxx[0]=4; 
        xxx[1]=5; 
        xxx[2]=-6;
    
        Vector v(xxx,size);// v = [4, 5, -6]
        Vector w(size);// w is a vector of size 3
    
        w = v+v; // w should be w=[8,10,-12]
        w.print();
    
        return 0;
    }
    

    【讨论】:

    • 非常感谢您的回答。真的行。程序在执行“w = v + v;”行时调用了两次析构函数。被破坏的实例都不是 v 或 w。我猜其中之一是 operator+ 中的 temp。但是另一个是什么?
    • 如果编译器不优化,可能会破坏两个对象。一种是在 + 运算符中构造的 temp。返回时,复制构造函数为包含 v + v 的临时对象构造一个新对象(+ 运算符按值返回)
    【解决方案4】:

    这样做是个坏主意:

    Vector::Vector(int* vector_coordinates, int vector_size){
        coordinates = vector_coordinates;
        size = new int;
        *size = vector_size;
    }
    

    您将坐标指针分配给您未分配的数据,然后尝试在析构函数中将其删除。

    但是你得到段错误的真正原因是你使用了默认的复制构造函数,并且 v 的临时副本在它死亡时删除了向量。您必须实现复制构造函数并确保深度复制或引用计数。

    试试这样的:

    Vector::Vector(const Vector& other){
        size = new int(*other.size);
        coordinates = new int[size];
        memcpy(coordinates, other.coordinates, sizeof(int)*(*size));
    }
    

    另外,如果你将 const 引用作为参数,你的 operator+ 会更有效率:

     Vector Vector::operator+ (const Vector& other)
    

    【讨论】:

    • 我使用 int* 为坐标动态分配内存,因为它们的大小可能会有所不同。我当然可以使用 int 作为向量的大小,但整个事情的目的是让我了解 C++ 上的一些东西......
    • 坐标可以,但是size应该是int字段,不是指针
    • 是的。但是,我相信我的 update-#1 是等效的,不是吗?
    • 我认为您的意思是更新 #2 - 是的,这就是我的意思。但是这种变化只在使用 operator+ 时避免了复制。它提高了速度(所以你应该这样做),但关于你的问题,它只是避免它,并没有解决它。你仍然需要实现复制构造函数和 operator= 来解决这个问题。
    • @PantelisSopasakis:您的 Update3 是一个非常糟糕的主意。它询问自己是否存在内存泄漏。请将您的 operator+ 签名更改为我在答案中提供的签名。您应该将 const 引用作为参数,但按值返回。
    【解决方案5】:

    考虑线

    w = v+v; // w should be w=[8,10,-12]
    

    为 v+v 的结果构造一个临时对象,然后分配给 w 并销毁。 由于您没有赋值运算符,因此默认实现会执行浅拷贝,并且您正在使用已释放的内存。

    解决此问题的简单方法是在为成员分配内存时实现复制构造函数/赋值运算符和析构函数。

    【讨论】:

    • 所以,每当我执行“w=v”时,编译器真正要做的是“w=*(new Vector(v))”,其中应该显式声明和实现 Vector(v),否则有些使用了我们无法完全控制的默认复制构造函数?
    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2012-12-22
    • 1970-01-01
    • 2013-07-24
    • 1970-01-01
    • 2013-12-21
    • 2014-11-06
    相关资源
    最近更新 更多