向量 class 重新实现为动态数组:复制构造函数出现内存错误

Vector class reimplementation as dynamic array: Memory faults with copy constructor

对于编程讲座,我正在尝试在自己的向量 class 中重新实现一些 C++ 标准向量 class' 的功能。我已经查看了几个类似的 classes 和实现,我的实现(除了使用初始值设定项列表)完全相同,到目前为止大多数功能都按预期工作。我故意让这个例子简单易读,至少可以减少出现语法错误的机会。

但是,我的复制构造函数和我想我的 push_back 函数导致了内存泄漏,我无法查明原因,即使在为它们重写了几次代码并将它们与类似的实现进行比较之后也是如此。

这是我的 vector.h:


#include<iostream>
#include <initializer_list>
using namespace std; 

class Vector {

private:
size_t sz;
size_t max_sz;
double* values;
static constexpr size_t min_sz {5};

public:

    //default constructor
    Vector()
    {
        values = new double[min_sz];
        sz = 0;
        max_sz = 5;
    }

    //constructor with submitted variable

    Vector(size_t n)
    {
        if (n < min_sz)
        {
            n = min_sz;
        }
        values = new double[n];
        sz = 0; 
        max_sz = n;

    }

    //copy constructor
    Vector (const Vector &v)
    {   
        max_sz=v.max_sz;
        sz=v.sz;
        values = new double[max_sz];

        for(size_t i=0;i<v.sz;i++)
        {
            values[i]=v.values[i];

        }

       /*
        cout << "[Copy constructor called with params Max_sz:" << max_sz << " and sz: " << sz;
        cout << "\n referenced vector contains: ";
        cout << v;
        cout << " | ";
        cout << "this vector contains: " << *this;
       */

    }

    Vector (std::initializer_list<double> list)
    {


        if(list.size() == 3)
        {
            std::initializer_list<double>::iterator it = list.begin();
            sz = *it++;
            max_sz = *it++;
            if (max_sz < min_sz)
            {
                max_sz = 5;
            }
            values = new double[max_sz];
            values[0] = *it;

        }



    }


    //destructor
    ~Vector()
    {
        delete[] values;
    }

    //functions

    size_t size() const
    {
        return sz;
    }

    bool empty() const
    {
        if (sz < 1) { return true; }

        return false;
    }


    void clear()
    {
        //set number of elements to 0, reserved memory isn't touched but can be overwritten by new assignments now

    sz = 0;

    }

    void reserve(size_t n)
    {
        if (n > max_sz)
        {
            double* tmp = new double[n];

            for (size_t i = 0; i < n; i++)
            {
                tmp[i] = values[i];
            }  

        delete [] values;
        values = tmp;

        }


    }


    void shrink_to_fit()
    {
        if (sz != max_sz)
        {
            double* tmp = new double[sz];

            for (size_t i = 0; i < sz; i++)
            {
                tmp[i] = values[i];
            }

            delete [] values;
            values = tmp;
        }
    } 

    void push_back(double x)
    {
        //check for space, if not enough space is allocated, allocate more

        if (sz+1 >= max_sz)
        {
            double* tmp = new double[max_sz*2];

            for (size_t i = 0; i < sz; i++)
            {
                tmp[i] = values[i];
            }

        delete [] values;
        values = tmp;
        max_sz = max_sz*2;

        }

        //then or otherwise, add the new element

        values[sz] = x;
        sz++;



    }

    void pop_back()
    {
        //remove last element in vector, throw exception if vector is empty

        if (sz == 0)
        {


        }
        else
        {
            //create new vector in memory with space for one element less

            sz--;

            double* tmp = new double[sz];

            for (size_t i = 0; i < sz; i++)
            {
                tmp[i] = values[i];
            }

            delete [] values;
            values = tmp;

        }




    }



    double& operator[](size_t index)
    {
        return values[index];
    }

    const double& operator[](size_t index) const
    {
        return values[index];
    }

    size_t capacity() const
    {
        return max_sz;
    }

    friend ostream& operator<< (ostream& out, const Vector& v)
    {
        out << "[";
        for (size_t i = 0; i < v.sz; i++)
        {
            out << v.values[i];
            if (i+1 < v.sz)
            {
                out << ",";
            }

        }

        out << "]";

        return out; 
    }


};

这里是一个导致内存错误的测试用例:

int main(){

  Vector y;
  cout << y << endl;
  {
    Vector x(0);
    for(double i{0};i<1;i+=0.1)
      x.push_back(i);
    cout << x << endl;
    x.pop_back();
    cout << x << endl;
    x.shrink_to_fit();
    cout << x << endl;
    x.push_back(10);
    const Vector tmp{x};
    const Vector tmp2{tmp};
    y = tmp2;
  }
  cout << y << endl;
  cerr << y << endl;
  Vector x{1,2,3};
  for(size_t i{4};i<10000;++i)
    x.push_back(i);
  cout << "Done" << endl;

  return 0;
}

这是我正在测试的编译器的 link,class 和测试用例已经保存:

https://rextester.com/EJFO89500

有人可以提示我代码中的缺陷在哪里吗?

一个问题是这一行:

y = tmp2;

该行使用了您未能实现的赋值运算符。最终发生的是调用默认赋值运算符,它只进行浅拷贝。因此,您在两个对象中复制了 values,并且析构函数在对同一值发出两次 delete[] 时会出现问题。

实现赋值运算符的最简单方法是使用 copy / swap idiom:

#include <algorithm>
//...
class Vector {
   public:
      Vector& operator=(const Vector& rhs)
      {
         if ( this != &rhs )
         {
            Vector temp(rhs);
            std::swap(temp.sz, sz);
            std::swap(temp.max_sz, max_sz);
            std::swap(temp.values, values);
        }
        return *this;
     }
//...
};

基本上,我们创建一个传入的临时文件Vector,并将当前内容与临时文件的内容进行交换。然后临时文件随着旧内容消失。