提问者:小点点

我不明白如何解决内存泄漏C++问题


条件

在讲座中,我们已经开始实施我们的向量。 在本任务中,您需要对其进行开发:添加sizecapacitypushback方法。 发送包含Simple_Vector类模板声明和定义的Simple_Vector.h头文件以进行验证:

要求:

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

决定的编写:

>

  • simple_vector.h:https://d3c33hcgiewv3.cloudfront.net/q-ol4qx_eeilzrlzf2wxfa_ac4e8270a5ff11e89fd0455a8819d387_simple_vector.h?expires=1596067200&签名

    simple_vector.cpp:https://d3c33hcgiewv3.cloudfront.net/uopveoaueeianar0yidmdg_bae6cec086ae11e88d9327752d64e780_simple_vector.cpp?expires=1596067200&签名

    评论:

    发送用于验证的头文件不应包括文件。 如果您启用了这些文件中的一个,您将得到一个编译错误。

    提示:

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

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

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

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

    下面是我的.h解决方案,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_;
    };
    

    提前谢谢你。


  • 共2个答案

    匿名用户

    由于缺乏异常安全性,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指针,您仍然需要用户定义的copy等。 如果使用相对于data的整数,则可以避免定义这些函数。

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

    P.P.S。 作为旁注:您正在实现的向量的行为与标准向量大不相同,这可能是您的老师有意为之的。 当我将一个对象添加到一个包含10个元素的向量中时,我期望只创建一个元素,并且由于重新定位可能会复制10个元素,而不是创建20个对象,其中9个不可访问。

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

    匿名用户

    我不会称其为泄漏,但您对end_的处理不一致。 您似乎将sizecapacity视为等价值,但它们不是。

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

    相关问题


    MySQL Query : SELECT * FROM v9_ask_question WHERE 1=1 AND question regexp '(不明白|解决|内存|泄漏|c++)' ORDER BY qid DESC LIMIT 20
    MySQL Error : Got error 'repetition-operator operand invalid' from regexp
    MySQL Errno : 1139
    Message : Got error 'repetition-operator operand invalid' from regexp
    Need Help?