如何在 C++ 中重新分配?
How to realloc in c++?
下面的代码构成了一个MCVE,重现了我想问的问题,但不是真正的代码。真正的代码要复杂得多,所以我写这篇文章来演示这个问题。
我正在寻找的关键功能是能够增长动态分配的数组,请不要建议使用 stl
,因为它被明确禁止。此代码用于教育目的,因此存在限制。
#include <cstring>
#include <iostream>
class Value
{
public:
Value(int value = 0);
Value(const Value &value);
Value &operator =(const Value &other);
~Value();
operator int() {return *m_absurdPointer;}
private:
int *m_absurdPointer;
};
Value::Value(int value) :
m_absurdPointer(new int[1])
{
*m_absurdPointer = value;
}
Value::Value(const Value &value)
{
m_absurdPointer = new int[1];
memcpy(m_absurdPointer, value.m_absurdPointer, sizeof(*m_absurdPointer));
}
Value &Value::operator =(const Value &other)
{
m_absurdPointer = new int[1];
memcpy(m_absurdPointer, other.m_absurdPointer, sizeof(*m_absurdPointer));
return *this;
}
Value::~Value()
{
delete[] m_absurdPointer;
}
class ValueArray
{
public:
ValueArray();
~ValueArray();
void append(const Value &value);
void show() const;
private:
Value *m_array;
unsigned int m_capacity;
unsigned int m_length;
};
ValueArray::ValueArray() :
m_array(nullptr)
, m_capacity(0)
, m_length(0)
{
}
ValueArray::~ValueArray()
{
delete[] m_array;
}
void
ValueArray::append(const Value &value)
{
if (m_length >= m_capacity)
{
Value *newarray;
unsigned int unitSize;
unitSize = 1;
newarray = new Value[m_capacity + unitSize];
if ((m_capacity > 0) && (m_array != nullptr))
memcpy(newarray, m_array, m_capacity * sizeof(*m_array));
delete[] m_array;
m_array = newarray;
m_capacity += unitSize;
}
m_array[m_length++] = value;
}
void
ValueArray::show() const
{
for (size_t i = 0 ; i < m_length ; ++i)
std::cout << static_cast<int>(m_array[i]) << std::endl;
}
int
main(void)
{
ValueArray example;
for (int i = 0 ; i < 10 ; ++i)
example.append(Value(i));
example.show();
return 0;
}
如您所见,它会导致双重 free
问题,因为 delete[] m_array;
在将值复制到重新new
编辑数组。
我尝试用 malloc()
/realloc()
来做到这一点,但我需要调用 Value()
的析构函数,所以 new
是强制性的,因为我不能使用free()
.
如何防止这种情况发生?如果我删除 delete[] m_absurdPointer;
,双重释放当然会消失,但会出现内存泄漏。
如果你必须坚持使用类似矢量的实现 ValueArray
:
,你应该使用移动构造函数
class Value
{
public:
Value(int value = 0);
Value(const Value &value);
Value(Value&& val);
Value &operator =(const Value &other);
Value &operator =(Value&& other);
~Value();
operator int() {return *m_absurdPointer;}
private:
int *m_absurdPointer;
};
Value::Value(Value&& o) : m_absurdPointer(o.m_absurdPointer) {
o.m_absurdPointer = nullptr;
}
Value &operator =(Value&& o) {
delete[] this->m_absurdPointer;
this->m_absurdPointer = o.m_absurdPointer;
o.m_absurdPointer = nullptr;
}
void
ValueArray::append(const Value &value)
{
if (m_length >= m_capacity)
{
Value *newarray;
unsigned int unitSize;
unitSize = 1;
newarray = new Value[m_capacity + unitSize];
if ((m_capacity > 0) && (m_array != nullptr)) {
std::move(m_array, m_array + m_length, newarray);
}
delete[] m_array;
m_array = newarray;
m_capacity += unitSize;
}
}
您基本上想要实现自己的向量 class,对吗?
好的,首先要做的是:据我所知,您不能增加以前分配的内存。至少不是标准分配器。
因此您需要分配一块新的更大的内存。
您可以使用新的标准方式执行此操作:
Type * newdata = new Type[size];
在这种情况下,将为每个新元素调用 class Type
的构造函数,即 size
次。
要将旧数据放入新数组,您需要将其复制或移动到那里:
for (size_t it = 0; it < oldsize; ++it) {
newdata[it] = olddata[it];
// newdata[it] = std::move(olddata[it]);
}
这就是 std::copy
的内容。 std::move
正在做。 (您也可以在循环中使用 std::swap
。)
为此,Type
class 需要默认构造函数和复制或移动赋值的有效实现。
您正在使用 memcpy
。在 C++ 中,这通常是一个坏主意:你实现的赋值运算符没有被调用,因此你的旧数组中的对象和原始副本都使用相同的指针,这就是为什么你得到 double free 的原因,很明显。
您还可以分配原始内存并使用 placement new 从旧对象复制或移动构造新对象:
void * memory = new char[size * sizeof(Type)];
for (size_t it = 0; it < oldsize; ++it) {
new (memory + it * sizeof(Type)) Type(olddata[it]); // copy
}
以上只是一个例子,真正的代码还需要考虑对齐
最后,我确信您可以通过某种方式欺骗默认分配器以释放您的(旧)内存而不破坏其中的对象,这允许您使用制作的原始副本 memcpy
。虽然这将是一个 hack 并且可能会破坏复杂的 classes,但这不是 C++ 的实现方式。
惯用的方法是将旧对象复制或移动到新存储(通过赋值或构造)。
下面的代码构成了一个MCVE,重现了我想问的问题,但不是真正的代码。真正的代码要复杂得多,所以我写这篇文章来演示这个问题。
我正在寻找的关键功能是能够增长动态分配的数组,请不要建议使用 stl
,因为它被明确禁止。此代码用于教育目的,因此存在限制。
#include <cstring>
#include <iostream>
class Value
{
public:
Value(int value = 0);
Value(const Value &value);
Value &operator =(const Value &other);
~Value();
operator int() {return *m_absurdPointer;}
private:
int *m_absurdPointer;
};
Value::Value(int value) :
m_absurdPointer(new int[1])
{
*m_absurdPointer = value;
}
Value::Value(const Value &value)
{
m_absurdPointer = new int[1];
memcpy(m_absurdPointer, value.m_absurdPointer, sizeof(*m_absurdPointer));
}
Value &Value::operator =(const Value &other)
{
m_absurdPointer = new int[1];
memcpy(m_absurdPointer, other.m_absurdPointer, sizeof(*m_absurdPointer));
return *this;
}
Value::~Value()
{
delete[] m_absurdPointer;
}
class ValueArray
{
public:
ValueArray();
~ValueArray();
void append(const Value &value);
void show() const;
private:
Value *m_array;
unsigned int m_capacity;
unsigned int m_length;
};
ValueArray::ValueArray() :
m_array(nullptr)
, m_capacity(0)
, m_length(0)
{
}
ValueArray::~ValueArray()
{
delete[] m_array;
}
void
ValueArray::append(const Value &value)
{
if (m_length >= m_capacity)
{
Value *newarray;
unsigned int unitSize;
unitSize = 1;
newarray = new Value[m_capacity + unitSize];
if ((m_capacity > 0) && (m_array != nullptr))
memcpy(newarray, m_array, m_capacity * sizeof(*m_array));
delete[] m_array;
m_array = newarray;
m_capacity += unitSize;
}
m_array[m_length++] = value;
}
void
ValueArray::show() const
{
for (size_t i = 0 ; i < m_length ; ++i)
std::cout << static_cast<int>(m_array[i]) << std::endl;
}
int
main(void)
{
ValueArray example;
for (int i = 0 ; i < 10 ; ++i)
example.append(Value(i));
example.show();
return 0;
}
如您所见,它会导致双重 free
问题,因为 delete[] m_array;
在将值复制到重新new
编辑数组。
我尝试用 malloc()
/realloc()
来做到这一点,但我需要调用 Value()
的析构函数,所以 new
是强制性的,因为我不能使用free()
.
如何防止这种情况发生?如果我删除 delete[] m_absurdPointer;
,双重释放当然会消失,但会出现内存泄漏。
如果你必须坚持使用类似矢量的实现 ValueArray
:
class Value
{
public:
Value(int value = 0);
Value(const Value &value);
Value(Value&& val);
Value &operator =(const Value &other);
Value &operator =(Value&& other);
~Value();
operator int() {return *m_absurdPointer;}
private:
int *m_absurdPointer;
};
Value::Value(Value&& o) : m_absurdPointer(o.m_absurdPointer) {
o.m_absurdPointer = nullptr;
}
Value &operator =(Value&& o) {
delete[] this->m_absurdPointer;
this->m_absurdPointer = o.m_absurdPointer;
o.m_absurdPointer = nullptr;
}
void
ValueArray::append(const Value &value)
{
if (m_length >= m_capacity)
{
Value *newarray;
unsigned int unitSize;
unitSize = 1;
newarray = new Value[m_capacity + unitSize];
if ((m_capacity > 0) && (m_array != nullptr)) {
std::move(m_array, m_array + m_length, newarray);
}
delete[] m_array;
m_array = newarray;
m_capacity += unitSize;
}
}
您基本上想要实现自己的向量 class,对吗?
好的,首先要做的是:据我所知,您不能增加以前分配的内存。至少不是标准分配器。
因此您需要分配一块新的更大的内存。
您可以使用新的标准方式执行此操作:
Type * newdata = new Type[size];
在这种情况下,将为每个新元素调用 class Type
的构造函数,即 size
次。
要将旧数据放入新数组,您需要将其复制或移动到那里:
for (size_t it = 0; it < oldsize; ++it) {
newdata[it] = olddata[it];
// newdata[it] = std::move(olddata[it]);
}
这就是 std::copy
的内容。 std::move
正在做。 (您也可以在循环中使用 std::swap
。)
为此,Type
class 需要默认构造函数和复制或移动赋值的有效实现。
您正在使用 memcpy
。在 C++ 中,这通常是一个坏主意:你实现的赋值运算符没有被调用,因此你的旧数组中的对象和原始副本都使用相同的指针,这就是为什么你得到 double free 的原因,很明显。
您还可以分配原始内存并使用 placement new 从旧对象复制或移动构造新对象:
void * memory = new char[size * sizeof(Type)];
for (size_t it = 0; it < oldsize; ++it) {
new (memory + it * sizeof(Type)) Type(olddata[it]); // copy
}
以上只是一个例子,真正的代码还需要考虑对齐
最后,我确信您可以通过某种方式欺骗默认分配器以释放您的(旧)内存而不破坏其中的对象,这允许您使用制作的原始副本 memcpy
。虽然这将是一个 hack 并且可能会破坏复杂的 classes,但这不是 C++ 的实现方式。
惯用的方法是将旧对象复制或移动到新存储(通过赋值或构造)。