0

I am sorry if this has already been covered before. I know how to do this is C and Java but not C++. Without using a pre-existing class which includes the use of Vector, how would you increase the size of an array given the code below?

The array expansion and assignment to the array takes place in push() noted with the all caps comment.

EDIT: As I have mentioned in comments below this is a question regarding manually reallocating arrays rather than using std::vector or "Dynamic Arrays."

Line.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;

};

Main.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;
}
TheWinterSnow
  • 175
  • 11
  • 6
    When you have a "dynamic array" of any kind in C++, the solution is *always* to use [`std::vector`](https://en.cppreference.com/w/cpp/container/vector). – Some programmer dude Nov 04 '18 at 08:27
  • 2
    I am aware of that. The purpose of this excessive is to manually reallocate memory similar to how std::vector would do it. I am aware this is extremely inefficient but again it is for the exercise of learning. – TheWinterSnow Nov 04 '18 at 08:31
  • 3
    If you know how to do this in C then you know how to do this in C++. Also see https://stackoverflow.com/q/3482941/390913 – perreal Nov 04 '18 at 08:40
  • 3
    This exercise should not use std::vector but should reallocated memory manually. – TheWinterSnow Nov 04 '18 at 09:44
  • 2
    Despite you saying you this question is not about using "Dynamic Arrays" I suspect you don't know what those are and that this question is *exactly* about using "Dynamic Arrays" (not `std::vector`). I think this is what you are looking for: https://stackoverflow.com/questions/17200649/a-function-to-resize-any-dynamic-array – Galik Nov 04 '18 at 10:17
  • 1
    This is actually a good question because it is not as trivial as it sounds and the proposed duplicates are not actually duplicates. – Galik Nov 04 '18 at 10:18
  • 1
    When someone wants to learn about dynamic arrays, there would always be another talking about `vector` instead of just teaching. I don't understand, isn't `std::vector` in some ways implemented by dynamic array? Isn't it essential to learn such a low level data structure? – Rick Nov 04 '18 at 10:24
  • Your code is ill-formed and discussing ill-formed code makes no sense. It is ill-formed, because `size` in `Point a[size]` is not a constant expression. Some compilers allow this construct with `size` being a variable, but this is a language extension and not C++ standard compliant. In other words, don't use and rely on it. – Walter Nov 04 '18 at 10:28
  • 5
    @Someprogrammerdude ...except when you are *writing* std::vector. – n. m. could be an AI Nov 04 '18 at 10:38
  • 1
    Tangentially related. `unsigned int index;` tells nothing. `size_t size;` is more self-explanatory, especially when declared next to `Point* points`. If you need to say that "this variable holds the size" in a comment, just call it `size`! – n. m. could be an AI Nov 04 '18 at 10:44
  • I can see using index or size for the variable name going both ways. On one hand the index is pointing to the next location in the array but at the same time it could be size because it is also storing the size of the array. Galik you are right. When it comes to the question it isn't as trivial as it would sound. And as Rick questioned about the question/problem being essential to low level programming that was the entire point of this question, to understand the lower level programming aspects for the sake of learning. – TheWinterSnow Nov 04 '18 at 11:28

4 Answers4

4

The simple answer is you should always use std::vector in this case. However it might be useful to explain just why that is. So lets consider how you would implement this without std::vector so you might see just why you would want to use 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;
}

This approach has many problems. We are forced to reallocate and move the entire array every time an entry is inserted. However a vector will pre-allocate a reserve and use space out of the reserve for each insert, only re-allocating space once the reserve limit is surpassed. This mean vector will far out perform your code in terms of performance as less time will be spent allocating and moving data unnecessarily. Next is the issue of exceptions, this implementation has no exception guarantees, where as the std::vector provides you with a strong exception guarantee: https://en.wikipedia.org/wiki/Exception_safety. Implementing a strong exception guarantee for your class is none trivial, however you would have automatically got this had you implemented this in terms of std::vector as such

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

There are also other more subtle problems with your approach, your class does not define copy or assignment operators and so gets compiler generated shallow copy versions generated which means if someone copies your class then allocated members will get deleted twice. To resolve this you need to follow the rule of 3 paradigm pre C++11 and the rule of 5 for C++ 11 onwards: https://en.wikipedia.org/wiki/Rule_of_three_(C%2B%2B_programming). However had you used a vector none of this would be needed as you would benefit from the rule of zero and be able to rely on the compiler generated defaults: https://blog.rmf.io/cxx11/rule-of-zero

Antony Peacock
  • 449
  • 4
  • 8
3

Essentially the only way is to use a dynamic array (one created using new[]) and to create an entirely new dynamic array and copy (or move) the objects from the old array to the new one.

Something like this:

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;

};

But this approach is very inefficient. To do this well is quite complex. The main problems with doing things this way are:

  • Exception safety - avoiding a memory leak if an exception is thrown.
  • Allocation - avoiding having to reallocate (and re-copy) every single time.
  • Move semantics - taking advantage of some objects ability to be moved much more efficiently than they are copied.

A (slightly) better version:

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

};

That takes care of exception safety and (to some degree - but not entirely) move semantics. However it must be pointed out that exception safety is only going to be complete if the elements stored in the array (type Point) are themselves exception safe when being copied or moved.

But this does not deal with efficient allocation. A std::vector will over allocate so it doesn't have to do it with every new element. This code also misses a few other tricks that a std::vector would employ (like allocating uninitialized memory and constructing/destructing the elements manually as and when they are needed/discarded).

Galik
  • 47,303
  • 4
  • 80
  • 117
  • Unfortunately your example still provides no exception guarantee. While you ensure you won't leak the class can still get into an inconsistent state if the point throws an exception during its assignment operation. In this case you would have already moved points into the new point array, which would get deleted when the exception was raised, leaving your class with an array of default constructed points and not the original values which are required to meet a basic exception guarantee. – Antony Peacock Nov 04 '18 at 10:55
  • @AntonyPeacock Yes very true. Just goes to show how non-trivial this actually is. Let me think on that... – Galik Nov 04 '18 at 10:56
  • Yes, exception safety is non-trivial. This would be best implemented via the copy-and-swap idiom: https://en.wikibooks.org/wiki/More_C%2B%2B_Idioms/Copy-and-swap – Antony Peacock Nov 04 '18 at 10:58
  • @AntonyPeacock I added a note that it can only be exception safe if the elements being stored in the array are themselves exception safe when being *moved*. I think this is as good as it can get. Copy & swap does not allow for moving... – Galik Nov 04 '18 at 11:01
2

You basically have no way but to allocate a new array, copy existing values inside and delete [] the old one. That's why vector is doing the reallocation by a multiplicative factor (say each reallocation doubles the size). This is one of the reasons you want to use the standard library structures instead of reimplementing.

Yuri Feldman
  • 2,354
  • 1
  • 20
  • 23
0

Keep It Simple

In my opinion, in this case, it's better to use a Linked-List of CPoint in CLine:

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;
};

.

Or, even better with smart pointers:

#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;
};
Amit G.
  • 2,546
  • 2
  • 22
  • 30