【问题标题】:I can't understand how to solve problem with memory leak C++我不明白如何解决内存泄漏 C++ 的问题
【发布时间】:2020-11-17 22:33:13
【问题描述】:

条件

在讲座中,我们已经开始实现我们的向量。在此任务中,您需要开发它:添加SizeCapacityPushBack 方法。发送包含SimpleVector类模板声明和定义的simple_vector.h头文件进行验证:

要求:

  • Capacity 方法应返回向量的当前容量 — 适合向量当前分配的内存块的元素数
  • Size 方法必须返回向量中的元素数
  • PushBack 方法在向量末尾添加一个新元素;如果当前分配的内存块(即Size() == Capacity())中没有剩余空间,向量必须分配一个大小为2 * Capacity()的块,将所有元素复制到其中,并删除旧的。
  • 对于新创建的对象,第一次调用 PushBack 方法必须使容量等于 1
  • Push Back 方法必须具有摊销常数复杂度
  • beginend 方法必须返回迭代器当前向量的开始和结束 向量分配的当前内存块必须在析构函数中释放
  • 另请参阅随附的解决方案模板,了解在单元测试中使用 SimpleVector 的其他要求。

决定的准备:

评论

您发送用于验证的头文件不应包含<vector><list><forward_list><deque><map> 文件。如果您启用了这些文件之一,您将收到编译错误。

提示

当然,SimpleVector 类模板的实现将有一个指针字段。在默认构造函数中,您需要使用一些东西对其进行初始化。在讲座中,我们只讨论了一种初始化指针的方法——使用 new 运算符。在 C++ 中,有一个特殊的值,表示一个不指向任何内容的指针——nullptr

int* p = nullptr;
string* q = nullptr;
map<string, vector<int>>* r = nullptr;

可以在默认构造函数中使用nullptr来初始化指针。

如何发送: 工作准备就绪后,您可以在“我的工作”选项卡上为任务的每个部分上传文件。

这是我的.hsolution,Coursera 测试系统会回复10 != 8: Memory leak detected。但是我无法弄清楚泄漏的去向。请帮帮我。

#pragma once
#include <cstdlib>

using namespace std;

template <typename T>
class SimpleVector {
public:
    SimpleVector() 
        : data(nullptr)
        , end_(data)
        , size_(0) {}

    explicit SimpleVector(size_t size) 
        : data(new T[size])
        , end_(data + size)
        , size_(size) {}

    ~SimpleVector() {
        delete[] data;
    }

    T& operator[](size_t index) { return data[index]; }

    T* begin() const { return data; }
    T* end() const { return end_; }

    size_t Capacity() const { return end_ - data; }
    
    size_t Size() const { return size_; }

    void PushBack(const T& value) {
        if (size_ == Capacity()) {
            if (size_ == 0) {
                delete[] data;  
                data = new T[1];
                data[size_] = value;
                ++size_; 
                end_ = data + size_;
            }
            else {
                T* local_data = new T[size_];
                
                for (size_t i = 0; i < size_; ++i) {
                    local_data[i] = data[i];
                }

                delete[] data;
                data = new T[2 * Capacity()];
                
                for (size_t i =0; i < size_; ++i) {
                    data[i] = local_data[i];
                }
                delete[] local_data;
                
                data[size_] = value;
                ++size_;
                end_ = data + size_ * 2;
            }
        } 
        else {      
            data[size_] = value;
            size_++;
        }
    }
private:
    T *data;
    T *end_;
    size_t size_;
};

谢谢提前。

【问题讨论】:

  • 你的班级违反了3/5/0的规则:https://en.cppreference.com/w/cpp/language/rule_of_three
  • 如果您使用带有非零参数的SimpleVector(size_t size) 构造函数,那么size_ == Capacity() 将为真,即使不需要,您也将重新分配内存。
  • 使用 unique_ptr.
  • @cigien 是的,我知道如何使用调试器,但在这种情况下它并不能帮助我找到错误
  • 在您的 PushBack 中,您不需要分配 2 个缓冲区并复制两次。

标签: c++ c++17


【解决方案1】:

由于缺乏异常安全性,PushBack 中存在内存泄漏。考虑:

T* local_data = new T[size_];
// potentially throwing operations here...
delete[] local_data;

如果这些操作抛出,那么delete[] local_data; 将永远不会被执行。

避免这种内存泄漏的典型方法是使用智能指针而不是裸指针来获得所有权。过时的方法是使用 try-catch。


您的类也未能强制类不变量 data 指针的唯一性。这样的约束对于析构函数的正确性至关重要,因为分配必须只删除一次,不能再删除。

复制类的实例将导致未定义的行为,因为在多个析构函数中删除了相同的指针。另一个后果是赋值运算符会泄漏之前分配的内存(在 UB 发生在析构函数之前):

{
    SimpleVector vec(42);
    SimpleVector another(1337);
    SimpleVector vec = another; // memory leak in assignment operator
} // undefined behaviour in the destructor

问题在于复制和移动构造函数和赋值运算符,它们是隐式生成的。隐式生成的特殊成员函数将复制指针值,违反其唯一性(并且在赋值的情况下无法删除先前的分配)。换句话说,这些函数执行的是浅拷贝。

使用智能指针作为成员是一个简单的解决方案。否则,您必须实现不泄漏也不违反唯一性的复制和移动构造函数和赋值运算符。

请注意,即使您确实使用了智能指针,由于end 指针,您仍然需要用户定义的副本等。如果您改为使用与data 相关的整数,则可以避免定义这些函数。


附:无需分配两次,复制两次。相反,分配一个更大的缓冲区,复制旧的,删除旧的,指向新的。


附言作为旁注:您正在实现的向量与标准向量的行为完全不同,这可能是您的老师故意的。当我将一个对象添加到包含 10 个元素的向量时,我希望只创建一个元素并且可能由于重定位而复制 10 个元素,而不是创建 20 个对象而其中 9 个无法访问。

向量的正确实现将内存分配和对象创建分离到该内存中,这允许内存的几何增长而不创建对象,直到它们被添加到向量中。我怀疑如何做到这一点超出了你的练习范围。

【讨论】:

    【解决方案2】:

    我不会称其为泄漏,但您对待end_ 的方式不一致。您似乎将 SizeCapacity 视为等效值,但它们不是。

    end_ 应该指向已分配(但不一定已填充)的内存,并且您在 end() 中返回 data + size,或者它应该指向最后一个元素后的一个,并且您应该存储 size_t capacity_ 而不是size_t size_;

    【讨论】:

      【解决方案3】:

      这是没有内存泄漏的解决方案。谢谢。

      #pragma once
      #include <cstdlib>
      
      using namespace std;
      
      template <typename T>
      class SimpleVector {
      public:
          SimpleVector() {
              data_ = nullptr;
              end_ = data_;
              size_ = 0;
              capacity_ = 0;
          }
          explicit SimpleVector(size_t size) {
              data_ = new T[size];
              end_ = data_ + size;
              size_ = size;
              capacity_ = size;
          }
      
          SimpleVector(const SimpleVector& that) 
              : data_(that.data_)
              , end_(that.end_)
              , size_(that.size_)
              , capacity_(that.capacity_) {}
      
          SimpleVector& operator = (const SimpleVector& that) {
              data_ = that.data_;
              end_ = that.end_;
              size_ = that.size_;
              capacity_ = that.capacity_;
          }
      
          ~SimpleVector() { delete[] data_; }
      
          T& operator[](size_t index) {
              return data_[index];
          }
      
          T* begin() const { return data_; }
          T* end() const { return data_ + size_; }
      
          size_t Capacity() const { return capacity_; }
          
          size_t Size() const { return size_; }
      
          void PushBack(const T& value) {
              if (size_ == capacity_) {
                  if (capacity_ == 0) { // т. е. создали конструктором по умолчанию, size_ = 0
                      data_ = new T[1];
                      capacity_ = 1;
                      data_[size_] = value;
                      ++size_; 
                      end_ = data_ + size_;
                  }
                  else if (capacity_ == size_) { // т. е. capacity_ == size_
                      T* local_data = new T[2 * size_];
                      for (size_t i = 0; i < size_; ++i) {
                          local_data[i] = data_[i];
                      }
                      
                      delete[] data_;
                      data_ = new T[2 * size_];
                      for (size_t i = 0; i < size_; ++i) {
                          data_[i] = local_data[i];
                      }
                      delete[] local_data;
                      data_[size_] = value;
                      size_++;
                      capacity_ *= 2;
                      end_ = data_ + size_;
                  }
              } 
              else {      
                  data_[size_] = value;
                  size_++;
              }
          }
      private:
          T *data_;
          T *end_;
          size_t size_;
          size_t capacity_;
      };
      

      【讨论】:

        猜你喜欢
        • 2011-05-28
        • 2012-05-23
        • 2012-08-19
        • 2010-12-11
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多