简体   繁体   中英

I can't understand how to solve problem with memory leak C++

Condition

In lectures, we have already started to implement our vector. In this task, you need to develop it: add the Size , Capacity , and PushBack methods. Send the simple_vector.h header file containing the SimpleVector class template declaration and definition for verification:

Requirements:

  • the Capacity method should return the current capacity of the vector — the number of elements that fit into the memory block currently allocated by the vector
  • the Size method must return the number of elements in the vector
  • the PushBack method adds a new element to the end of the vector; if there is no free space left in the current allocated memory block (ie Size() == Capacity() ), the vector must allocate a block of size 2 * Capacity() , copy all the elements to it, and delete the old one.
  • the first call to the PushBack method for a newly created object must make the capacity equal to one
  • the Push Back method must have a amortized constant complexity
  • the begin and end methods must return iterators the current beginning and end of the vector the current memory block allocated by the vector must be freed in the destructor
  • also see the attached solution template for additional requirements for working with SimpleVector in unit tests.

The preparation of the decision:

Comment :

The header file that you send for verification should not include the <vector> , <list> , <forward_list> , <deque> , <map> files. If you have one of these files enabled, you will get a compilation error.

Hint :

For sure, your implementation of the SimpleVector class template will have a field that is a pointer. In the default constructor, you will need to initialize it with something. In the lectures, we only discussed one way to initialize pointers — using the new operator. In C++, there is a special value that means a pointer that points to nothing — nullptr :

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

You can use nullptr to initialize the pointer in the default constructor.

How to send: When the work is ready, you can upload files for each part of the task on the 'My work'tab.

And here is my .h solution to which the Coursera testing system responds a 10:= 8: Memory leak detected . However I can't figure out where the leak is going. Help me pls.

#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_;
};

Thank you in advance.

There is a memory leak in PushBack due to lack of exception safety. Consider:

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

If those operations throw, then delete[] local_data; will never be executed.

Typical way to avoid such memory leak is to use smart pointers instead of bare pointers for ownership. The antiquated way is to use try-catch.


Your class also fails to enforce the class invariant of uniqueness of data pointer. Such constraint is essential for the destructor to be correct, because an allocation must be deleted exactly once, and no more.

Making a copy of an instance of the class will result in undefined behaviour because of same pointer being deleted in multiple destructors. Another consequence is that the assigment operators will leak the previously allocated memory (before the UB occurs in the destructor):

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

The problem is in the copy and move constructors and assignment operators, which you've left as implicitly generated. The implicitly generated special member functions will copy the pointer value, violating its uniqueness (and failing to delete the previous allocation in case of assignment). In other words, those functions perform a shallow copy.

Using a smart pointer as the member is an easy solution. Otherwise, you must implement copy and move constructors and assignment operators that don't leak, nor violate uniqueness.

Note that even if you did use a smart pointer, you'd still need user defined copy etc. because of the end pointer. If you instead used an integer that is relative to data , then you could avoid defining those functions.


PS There is no need to allocate twice, and copy twice. Instead, allocate one larger buffer, copy the old one, delete the old, point to the new.


PPS As a sidenote: The vector you are implementing behaves quite differently from the standard vector, which is probably intentional by your teacher. When I add an object to a vector of 10 elements, I would expect only one element to be created and possibly 10 be copied due to relocation, rather than 20 objects being created with 9 being unaccessible.

A proper implementation of vector separates the allocation of memory, and creation of objects into that memory which allows the growth of the memory to be geometric without creating objects until they are added into the vector. I suspect that how to do this is outside the scope of your exercise.

I wouldn't call it a leak, but you treat end_ inconsistently. It seems like you are treating Size and Capacity as equivalent values, they are not.

Either end_ should point one past the allocated (but not necessarily populated) memory, and you return data + size in end() , or it should point one past the last element, and you should store size_t capacity_ not size_t size_ ;

Here is solution without memory leak. Thank you.

#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_;
};

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM