提问人:Dingus 提问时间:12/4/2020 更新时间:12/6/2020 访问量:212
C++ 我的复制构造函数无法将内存移动到新区域
C++ my copy constructor fails to move the memory to a new area
问:
所以我最近开始努力让我自己的向量类工作,但我有点卡在我的复制构造函数上。我显然是 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)
我不希望任何人抽出时间来调试我的代码,我不想这样。我只是要求一双专业的眼睛来瞥一眼它并帮助新手。提前致谢。
答:
您的代码存在多个问题。
第一个也是最重要的一个是:
T* storage = new T[capacity];
这与成员变量不同。它是一个局部变量,恰好具有相同的名称。复制构造函数完成后,除了泄漏内存外,您没有执行任何操作。storage
storage
此外,您还有以下功能:
vector(const vector &other) : storage(other.storage),
这会将指针分配给 。这实际上就是双重自由的来源。您正在执行浅层复制,因此当 和 被销毁时,将在析构函数中的调用中使用相同的指针值。other.storage
this
this
other
delete []
第三个问题是:
memcpy(storage, other.storage, sizeof(T) * capacity);
这不适用于不可简单复制的类型。假设您解决了除此问题之外的所有问题。此代码将惨遭失败:
vector<std::string> s;
原因是你不能用来复制对象,因为它不是很容易复制的。memcpy
std::string
std::string
解决方法是使用 ,而不是 ,因为(应该)足够聪明,可以对可复制的类型执行 操作,或者对不可复制的类型执行普通循环。std::copy
memcpy
std::copy
memcpy
最后一个问题是你对类的命名。请注意,C++中已经有一个。要么将名称更改为其他名称,要么将类放在其自己的命名空间中,这样在某个位置发生名称冲突就不会发生。vector
std::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;
}
以上是用复制/交换的成语
这个问题很简单,但在你自己的代码中很难看到,因为你知道你想让它做什么。您可以通过在调试器中单步执行并仔细检查每一行的值和地址来跟踪它。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::copy
memcpy
评论
T* storage = new T[capacity];
storage(other.storage)
memcpy
仅适用于可复制的简单对象。 没关系,你不能将你的实现与任何不可复制的对象一起使用。int
memcpy
--不。用。std::copy