C++ 我的复制构造函数无法将内存移动到新区域

C++ my copy constructor fails to move the memory to a new area

所以我最近开始着手让我自己的向量 class 工作并且我有点卡在我的复制构造函数上。我显然是 c++ 的新手,希望堆栈溢出方面的好人能帮我一些忙。所以我得到了这个复制构造函数,它复制了正在使用的实际 ptr、ptr 的结束索引(用户可以使用的元素)和 ptr 拥有的实际 capacity/reserved 内存,加上已用内存的大小。

    vector(const vector &other) : storage(other.storage), capacity(other.capacity), 
        endIndex(other.endIndex), m_size(other.m_size)
    {
        T* storage = new T[capacity];
        memcpy(storage, other.storage, sizeof(T) * capacity);
    }

问题在于,虽然它看似成功地复制了信息,但如果其中一个对象 运行 超出范围,则信息或至少其中的一部分会被删除。如果我还在其中一个矢量对象上执行 push_back,它会同时发生在它们两个上。所以说他们共享他们的 ptr 地址是相当安全的。例如,如果我 运行 这个代码在我的主函数中

int main()
{

    vector<int> vec;
    vec.push_back(5);
    vec.push_back(55);
    vec.push_back(500);
    vector<int> vec1 = vec;

    for (int i = 0; i < vec1.size(); i++)
    {
        std::cout << vec1[i] << std::endl;
    }
    return 0;
}

我会收到此错误消息

5
55
500
free(): double free detected in tcache 2
Aborted (core dumped)

我假设这是因为 ptr 在循环过程中被删除,它反过来以一种很好的方式使程序崩溃。 push_back 的另一个例子是

int main()
{

    vector<int> vec;
    vec.push_back(5);
    vec.push_back(55);
    vec.push_back(500);
    vector<int> vec1 = vec;
    vec.push_back(55);

    for (int i = 0; i < vec1.size() + 1; i++)
    {
        std::cout << vec1[i] << std::endl;
    }
    return 0;
}

你可以明显地看到我实际上 push_back 在原始矢量对象上而不是新矢量上,我什至必须增加 for-loops 范围才能看到新矢量上的对象, 提示新对象中的整数大小与之前没有变化。此代码的输出是:

5
55
500
55
free(): double free detected in tcache 2
Aborted (core dumped)

我不希望任何人抽出时间来调试我的代码,我不希望这样。我只是要求一双专业的眼睛来浏览它并帮助新手。提前致谢。

您的代码存在多个问题。

第一个也是最重要的是:

T* storage = new T[capacity];

那个storage与成员变量storage一样。它是一个局部变量,恰好具有相同的名称。复制构造函数完成后,除了泄漏内存,你什么都没做。


另外,你还有这个:

vector(const vector &other) : storage(other.storage),

这会将指针 other.storage 分配给 this。这实际上是 double-free 的来源。您正在执行浅拷贝,因此当 thisother 被销毁时,将在析构函数中调用 delete [] 时使用相同的指针值。


第三期是这样的:

memcpy(storage, other.storage, sizeof(T) * capacity);

这不适用于不可平凡复制的类型。假设您解决了除此问题之外的所有问题。这段代码会失败得很惨:

vector<std::string> s;

原因是您不能使用 memcpy 复制 std::string 对象,因为 std::string 不可简单复制。

解决方法是使用 std::copy,而不是 memcpy,因为 std::copy 是(应该)足够聪明,可以为平凡可复制的类型执行 memcpy ,或用于非平凡可复制类型的普通循环。


最后一期是你对classvector的命名。请注意,C++ 中已经有一个 std::vector。要么将名称更改为其他名称,要么将您的 class 放在它自己的命名空间中,这样如果您碰巧在某个地方 #include <vector> 就不会发生名称冲突。

将所有这些放在一起,您将得到这个(未编译,请原谅任何语法错误):


#include <algorithm>

namespace myVector
{
    template <typename T>
    class vector
    {
       private:
            // your member variables
       public:
         //... 
         vector(const vector &other) : capacity(other.capacity), 
                endIndex(other.endIndex), m_size(other.m_size)
        {
          storage = new T[capacity]();
          std::copy(other.storage, other.storage + other.m_size, storage);
        }

        vector& operator=(const vector& other) 
        {
            // see later
        }

        ~vector()
        {
           delete [] storage;
        } 
      //...
  };
}  
     

那么 main 可能是这样的:

#include <myvector>

int main()
{
    myVector::vector<int> vec;
    vec.push_back(5);
    vec.push_back(55);
    vec.push_back(500);
    myVector::vector<int> vec1 = vec;

    for (int i = 0; i < vec1.size(); i++)
    {
        std::cout << vec1[i] << std::endl;
    }
    return 0;
}

完成并更正后,要完成 3 的规则,赋值运算符可以简单地是这样的:

vector& operator=(const vector& other)
{
   if ( &other != this )
   { 
       vector temp(other);
       std::swap(temp.capacity, capacity);
       std::swap(temp.m_size, m_size);
       std::swap(temp.endIndex, endIndex);
       std::swap(temp.storage, storage);
   } 
   return *this;
}

以上是使用copy/swap idiom

问题很简单,但很难在您自己的代码中看到,因为您知道自己想要它做什么。您可以通过在调试器中逐步执行并在每一行上仔细检查 storage 的值 和地址 来追踪它。

说真的,先试试看。


好的,就是这样:

vector(const vector &other)
: storage(other.storage)    // 1. copy the pointer, so this->storage is shared
, capacity(other.capacity)
, endIndex(other.endIndex)
, m_size(other.m_size)
{
    // 2. declare a local variable called storage which shadows this->storage
    T* storage = new T[capacity];
    memcpy(storage, other.storage, sizeof(T) * capacity);
}

不希望共享存储,因此您不应该初始化storage(other.storage)。实际上没有理由在您的代码中执行此操作。如果你只是将它初始化为 nullptr 你会很快意识到构造函数主体中的局部变量是错误的。

只需从 T* storage = ... 中删除 T* 即可解决您眼前的问题。关于使用 std::copy 而不是 memcpy 以及如何更好地构建代码的所有其他建议都是很好的建议,您也应该这样做。