在C++中手动调整数组大小

Manually resize array in C++

本文关键字:数组 调整 C++      更新时间:2023-10-16

如果之前已经讨论过,我很抱歉。我知道如何做到这一点是C和Java,但不是C++。如果不使用包含Vector的预先存在的类,在给定以下代码的情况下,如何增加数组的大小?

数组的扩展和分配在push((中进行,用全大写注释进行注释。

编辑:正如我在下面的评论中提到的,这是一个关于手动重新分配数组而不是使用std::vector或"动态数组"的问题

第h行

#include <iostream>
#include "Point.h"
using namespace std;
class Line {
public:
Line();
virtual ~Line();
// TAKE IN NEW POINT, INCREASE THE ARRAY SIZE AND ADD NEW POINT TO THE END OF THE ARRAY
void push(const Point& p);
private:
unsigned int index;  // size of "points" array
Point* points;
};

主要.cpp

#include <iostream>
#include "Point.h"
#include "Line.h"
using namespace std;
int main() {
int x, y;
int size;           // Some user defined size for the array
Line line;
Point a[size];      // Some points that are already filled
// Push the data in a[] to the variable "line"
for(int i = 0; i < size; i++){
// Increase array size of Point* points in variable line and add a[i] to the end of the array
line.push(points[i]);
}
return 0;
}

简单的答案是,在这种情况下,您应该始终使用std::vector。然而,解释一下为什么会这样可能会很有用。所以,让我们考虑一下在没有std::vector的情况下如何实现这一点,这样你就可以明白为什么要使用std::vector:

// Naive approach
Line::push(const Point& p)
{
Point* new_points = new Points[index + 1];
std::copy(std::make_move_iterator(points), std::make_move_iterator(points+index), new_points);
new_points[index] = p;
delete[] points;
points = new_points;
index += 1;
}

这种方法有很多问题。每次插入条目时,我们都被迫重新分配和移动整个数组。然而,向量将预先分配一个保留,并为每个插入使用保留之外的空间,只有在超过保留限制时才重新分配空间。就性能而言,这个平均向量将远远优于代码,因为不必要地分配和移动数据所花费的时间将减少。接下来是异常问题,此实现没有异常保证,因为std::vector为您提供了强大的异常保证:https://en.wikipedia.org/wiki/Exception_safety.为您的类实现一个强大的异常保证绝非易事,然而,如果您按照这样的std::vector实现了这一点,您就会自动得到它

Line::push(const Point& p)
{
points.push_back(p);
}

您的方法还有其他更微妙的问题,您的类没有定义复制或赋值运算符,因此会生成编译器生成的浅复制版本,这意味着如果有人复制了您的类,则分配的成员将被删除两次。要解决这个问题,您需要遵循C++11之前的3范式规则和C++11之后的5规则:https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming(。然而,如果您使用向量,则不需要这些,因为您将受益于零规则,并且能够依赖编译器生成的默认值:https://blog.rmf.io/cxx11/rule-of-zero

本质上,唯一的方法是使用动态数组(使用new[]创建的数组(,并创建一个全新的动力学数组组复制(或移动(到新数组。

类似这样的东西:

class Line {
public:
Line(): index(0), points(nullptr) {} // initialize
virtual ~Line() { delete[] points; } // Clean up!
void push(const Point& p)
{
// create new array one element larger than before
auto new_points = new Point[index + 1];
// copy old elements to new array (if any)
for(unsigned int p = 0; p < index; ++p)
new_points[p] = points[p];
new_points[index] = p; // then add our new Point to the end
++index; // increase the recorded number of elements
delete[] points; // out with the old
points = new_points; // in with the new

}
private:
unsigned int index;  // size of "points" array
Point* points;
};

但这种方法效率很低。要做好这件事相当复杂。以这种方式做事的主要问题是:

  • 异常安全-在引发异常时避免内存泄漏
  • 分配-避免每次都要重新分配(和重新复制(
  • 移动语义-利用某些对象被移动的能力比它们被复制的效率高得多

更好的版本:

class Line {
public:
Line(): index(0) {} // initialize
virtual ~Line() { } // No need to clean up because of `std::unique_ptr`
void push(const Point& p)
{
// create new array one element larger than before
auto new_points = std::unique_ptr<Point[]>(new Point[index + 1]);    
// first add our new Point to the end (in case of an exception)
new_points[index] = p; 
// then copy/move old elements to new array (if any)
for(unsigned int p = 0; p < index; ++p)
new_points[p] = std::move(points[p]); // try to move else copy
++index; // increase the recorded number of elements
std::swap(points, new_points); // swap the pointers
}
private:
unsigned int index;  // size of "points" array
std::unique_ptr<Point[]> points; // Exception safer
};

这涉及到异常安全和(在某种程度上但并非完全(移动语义。但是,必须指出的是,只有当被复制移动时,存储在数组中的元素(类型Point(本身是异常安全例外安全才会是完整。

但这并不涉及有效分配。std::vector过度分配,因此不必对每个新元素都这样做。此代码还错过了std::vector可能使用的其他一些技巧(如分配未初始化的内存,以及在需要/丢弃元素时手动构建/销毁元素(。

您基本上没有办法,只能分配一个新数组,复制里面的现有值,并delete []旧数组。这就是矢量通过乘法因子进行重新分配的原因(比如每次重新分配都会使大小翻倍(。这就是您希望使用标准库结构而不是重新实现的原因之一。

保持简单

在我看来,在这种情况下,最好在CLine:中使用CPoint的链表

struct CPoint
{
int x = 0, y = 0;
CPoint * m_next = nullptr;
};

class CLine
{
public:
CLine() {};
virtual ~CLine()
{
// Free Linked-List:
while (m_points != nullptr) {
m_current = m_points->m_next;
delete m_points;
m_points = m_current;
}
};
// TAKE IN NEW POINT, INCREASE THE ARRAY SIZE AND ADD NEW POINT TO THE END OF THE ARRAY
void push(const CPoint& p)
{
m_current = (((m_points == nullptr) ? (m_points) : (m_current->m_next)) = new CPoint);
m_current->m_x = p.m_x;
m_current->m_y = p.m_y;
m_index++;
};
private:
unsigned int m_index = 0;  // size of "points" array
CPoint * m_points = nullptr, * m_current = nullptr;
};

或者,使用智能指针更好:

#include <memory>
struct CPoint
{
int m_x = 0, m_y = 0;
std::shared_ptr<CPoint> m_next;
};

class CLine
{
public:
CLine() {};
virtual ~CLine() {}
// TAKE IN NEW POINT, INCREASE THE ARRAY SIZE AND ADD NEW POINT TO THE END OF THE ARRAY
void push(const CPoint& p)
{
m_current = (((m_points == nullptr) ? (m_points) : (m_current->m_next)) = std::make_shared<CPoint>());
m_current->m_x = p.m_x;
m_current->m_y = p.m_y;
m_index++;
};
private:
unsigned int m_index = 0;  // size of "points" array
std::shared_ptr<CPoint> m_points, m_current;
};