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

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

提问人:Dingus 提问时间:12/4/2020 更新时间:12/6/2020 访问量:212

问:

所以我最近开始努力让我自己的向量类工作,但我有点卡在我的复制构造函数上。我显然是 c++ 的新手,希望 stack overflow 的好人能帮到我一点。因此,我得到了这个复制构造函数,它复制了正在使用的实际 ptr、ptr 的结束索引(用户可以使用的元素)和 ptr 拥有的实际容量/保留内存,以及已用内存的大小。

    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 的地址。例如,如果我在我的 main 函数中运行此代码

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 循环范围才能在新向量上看到对象,暗示新对象中的大小整数与以前没有变化。此代码的输出为:

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

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

C++ 模板 Vector Copy-constructor

评论

7赞 Some programmer dude 12/4/2020
问题出在复制构造函数中。想想它的作用。想想初始值设定项是做什么的。T* storage = new T[capacity];storage(other.storage)
0赞 Kaldrr 12/4/2020
使用调试器单步执行程序,并精确地查看两个向量的成员,也许这会让你知道出了什么问题。
1赞 t.niese 12/4/2020
仅供参考,在没有未定义行为的情况下以标准符合的方式实现向量仍然是一个问题:在没有未定义行为的情况下实现类似 std::vector 的容器,并且P0593R6隐式创建对象以进行低级对象操作。通常它会起作用,因为对于市长编译器来说,它知道他们在这些特定情况下将如何工作。但这是一个需要注意的重要信息。
0赞 t.niese 12/4/2020
memcpy仅适用于可复制的简单对象。 没关系,你不能将你的实现与任何不可复制的对象一起使用。int
3赞 PaulMcKenzie 12/4/2020
memcpy--不。用。std::copy

答:

3赞 PaulMcKenzie 12/4/2020 #1

您的代码存在多个问题。

第一个也是最重要的一个是:

T* storage = new T[capacity];

这与成员变量不同。它是一个局部变量,恰好具有相同的名称。复制构造函数完成后,除了泄漏内存外,您没有执行任何操作。storagestorage


此外,您还有以下功能:

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

这会将指针分配给 。这实际上就是双重自由的来源。您正在执行浅层复制,因此当 和 被销毁时,将在析构函数中的调用中使用相同的指针值。other.storagethisthisotherdelete []


第三个问题是:

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

这不适用于不可简单复制的类型。假设您解决了除此问题之外的所有问题。此代码将惨遭失败:

vector<std::string> s;

原因是你不能用来复制对象,因为它不是很容易复制的。memcpystd::stringstd::string

解决方法是使用 ,而不是 ,因为(应该)足够聪明,可以对可复制的类型执行 操作,或者对不可复制的类型执行普通循环。std::copymemcpystd::copymemcpy


最后一个问题是你对类的命名。请注意,C++中已经有一个。要么将名称更改为其他名称,要么将类放在其自己的命名空间中,这样在某个位置发生名称冲突就不会发生。vectorstd::vector#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;
}

以上是用复制/交换的成语

2赞 Useless 12/4/2020 #2

这个问题很简单,但在你自己的代码中很难看到,因为你知道你想让它做什么。您可以通过在调试器中单步执行并仔细检查每一行的值和地址来跟踪它。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

只需删除 from 即可解决您的直接问题。所有其他关于使用代替 的建议以及如何更好地构建代码都是很好的建议,您也应该这样做。T*T* storage = ...std::copymemcpy