Search code examples
c++arraysdynamic-memory-allocation

Can we create a vector like mechanism?


I know that vectors exist, but its just for practice. I was trying to increase the size of arrays on heap. There is one quest already asked related to this but still i couldn't find my mistake.

structure is like this : 1 array on stack, containing 2 pointer to int array. Those 2 int arrays are on heap. The program checks if there is empty space in 1st array and if no, then it copies the pre existing elements to 2nd array > delete 1st one > create 1st with 1 bigger size > copy the elements back > then add the last element.

The problem is with adding the new element. Error says " Buffer overrun while writing to arr[0] ".

Why is the last element not inside the array boundary? (Also in my code, i have considered element = 0 as empty.)


#include <iostream>
using namespace std;

int main()
{
    cout << "We are trying to create Vector like mechanism.\n \n" << endl;

    int* arr[2]{ nullptr };  // declared an array which contains ptrs
    arr[0] = new int[3] {};  // making 1st array on heap

    for (int i{1}; i > 0; i++) {
        char c1;             // choice 1
        cout << "Do you want to add elements : "; cin >> c1;

        if (c1 == 'y' || c1 == 'Y') {
            int e;                                 // element to be entered
            bool a{false};                         // this is a checkbox that the element is succesfully entered or not
            int n = sizeof(arr[0]) / sizeof(int);  // num of elements in arr[0]
            
            for (int j{}; j < n; j++) {
                if ((arr[0])[j] == 0) {            // to check wether the arrays have vacancy or not
                    cout << "Enter the element to be entered : "; cin >> e;
                    (arr[0])[j] = e;
                    a = true;
                    break;
                }
            }
            if (a == false) {
                // copy 1 to 2 -> delete 1 > create new at 1 > copy 2 to 1 > add new element to 1 > delete 2
                
                arr[1] = new int[n];             // creating 2nd array
                for (int k{}; k < n; k++) {      // copying the elements
                    (arr[1])[k] = (arr[0])[k];
                }
                delete[] arr[0];                 // deleting 1st
                arr[0] = new int[n + 1] {};      // recreating 1st
                for (int l{}; l < n; l++) {      // copying elements back to 1st
                    (arr[0])[l] = (arr[1])[l];
                }

                for (int t{}; t < n + 1; t++) {
                    cout << (arr[0])[t] << "  ";
                }
                delete[] arr[1];                 // deleting 2nd array


                cout << "Enter the element to be entered : "; cin >> e;
                (arr[0])[n + 1] = e;             // inputing the new element
                a = true;
            }
        }
        else if (c1 == 'n' || c1 == 'N') {
            cout << "OK, no new element was entered." << endl;
            break;
        }
        else {
            cout << "Enter a valid choice. ie.( Y/y for YES and N/n for NO)" << endl;
        }
    }


    delete[] arr[0]; delete[] arr[1];


}

Solution

  • Judging by the code in the question, the OP has not yet studied classes.

    When he does, he will discover that the functions below are remarkably similar to the member functions described in the video I cited in the comments.

    Arthur O'Dwyer explains everything you need to know—and supplies source code—for a "roll-your-own" vector class, in this excellent talk from CppCon 2019: Back to Basics: RAII and the Rule of Zero.

    I am impressed that a relatively new C++ programmer is interested to learn about dynamically allocated arrays. Admittedly, this is a somewhat advanced topic. Moreover, in production code, you should not be reinventing the wheel. Class std::vector does a better job than you probably could.

    That said, it is worthwhile to learn how it's done. Sooner or later, all C++ programmers take a stab at it.

    The code in the OP is rather far-off from what O'Dwyer teaches, and I have not tried to "correct" it. In addition, I am not showing how this is done with RAII classes, which is the subject of O'Dwyer's talk. Instead, I have tried to stick to the the kind of functions that the OP already understands.

    There are plenty of explanatory comments in the program below. Pay special attention to function my_vec_resize. It explains the careful sequence that must be used to safely increase (or decrease) the size of an array. Note, as well, that the functions my_vec_destruct, my_vec_clone, and my_vec_assign effectively implement the Rule of Three that is the hallmark of an RAII class.

    Function main, at the bottom, is a test driver that demonstrates how these functions work together. It's a good place to start reading. There are no pointers in function main, nor any calls to operator new[] or operator delete[]. Those low-level implementation details have been hidden in the family of my_vec functions, which is how it should be.

    // main.cpp
    #include <algorithm>  // copy, equal, min, swap
    #include <cstddef>    // size_t
    #include <iostream>   // cout, ostream
    
    struct my_vec
    {
        int* data{ nullptr };
        std::size_t size{};
    };
    my_vec my_vec_construct(std::size_t const n)
    {
        // Analogous to "constructor" function of a class.
        my_vec v;
        v.data = new int[n] {};  // could throw `std::bad_alloc`
        v.size = n;
        return v;
    }
    void my_vec_destruct(my_vec& v) noexcept
    {
        // Analogous to "destructor" function of a class.
        delete[] v.data;
        v.data = nullptr;
        v.size = 0u;
    }
    my_vec my_vec_clone(my_vec const& source)
    {
        // Return a "deep" copy of the argument `source`.
        // Analogous to "copy constructor" function of a class.
        my_vec target{ my_vec_construct(source.size) };
    
        // Use the Standard Library function `std::copy`, to copy the elements.
        // It has three arguments:
        //   1. pointer to beginning of source data
        //   2. pointer to "one-beyond-the-last" source data
        //   3. pointer to target
        // These pointers can also be "iterators," but that's another subject.
        std::copy(source.data, source.data + source.size, target.data);
        return target;
    }
    void swap(my_vec& a, my_vec& b) noexcept
    {
        using std::swap;
        swap(a.data, b.data);
        swap(a.size, b.size);
    }
    void my_vec_assign(my_vec const& source, my_vec& target)
    {
        // Use the "copy-and-swap" idiom to assign a copy of argument `source`
        // to the second argument `target`.
        // Analogous to "copy-assignment" operator of a class.
        //
        // Note: the "cloning" operation could throw `std::bad_alloc`.
        // When it does, we leave `target` unaltered.
        my_vec tmp{ my_vec_clone(source) };
        swap(tmp, target);
        my_vec_destruct(tmp);  // avoid memory leaks!
    }
    void my_vec_resize(my_vec& v, std::size_t const new_size)
    {
        // Analogous to "resize" function of class `std::vector`.
        if (v.size == new_size)
            return;
    
        // It is possible that the allocation will fail. That is why 
        // we attempt the allocation first, before doing anything else.
        // If it fails, we leave the original vector unaltered.
        // 
        // When operator `new` fails, it will throw a `std::bad_alloc`.
        // That will interrupt this function, and probably cause the 
        // program to abort. Otherwise, when the allocation works, we 
        // continue execution below, where data elements are copied.
        auto p{ new int[new_size] {} };  // elements are initialized to 0.
    
        // Allocation succeeded. Copy the elements.
        // We use `std::min`, in case the `new_size` is smaller
        // than the old size.
        std::size_t size_to_copy{ std::min(v.size, new_size) };
        std::copy(v.data, v.data + size_to_copy, p);
    
        // Important: avoid memory leaks. Delete the existing array.
        // Be sure to use the array form of operator `delete`.
        delete[] v.data;
    
        // Now, you can set `v.data` to point to the new array.
        v.data = p;
        p = nullptr;  // optional, `p` is a "local" variable that will be 
                      // discarded anyway.
    
        // Don't forget to update the size.
        v.size = new_size;
    }
    void my_vec_grow(my_vec& v) {
        // Double the size of v.
        std::size_t new_size{ v.size == 0u ? 2u : v.size + v.size };
        my_vec_resize(v, new_size);
    }
    bool my_vec_empty(my_vec const& v) noexcept {
        return v.size == 0u;
    }
    bool operator==(my_vec const& a, my_vec const& b) noexcept {
        // `std::equal` has the same three arguments as `std::copy`.
        return a.size == b.size
            && std::equal(a.data, a.data + a.size, b.data);
    }
    bool operator!=(my_vec const& a, my_vec const& b) noexcept {
        return !(a == b);
    }
    std::ostream& operator<<(std::ostream& ost, my_vec const& v)
    {
        ost.put('[');
        if (!my_vec_empty(v)) {
            ost << v.data[0u];
            for (std::size_t i{ 1u }; i < v.size; ++i) {
                ost << ',' << v.data[i];
            }
        }
        ost.put(']');
        return ost;
    }
    int main()
    {
        // Constructor sets all elements to 0.
        my_vec a{ my_vec_construct(2) };
        std::cout << "Post construction --> a: " << a << "\n\n";
    
        // Assign a couple of values
        a.data[0] = 7;
        a.data[1] = 11;
        std::cout << "Post assign elements --> a: " << a << "\n\n";
    
        // Try cloning.
        auto b{ my_vec_clone(a) };
        std::cout << "Clone --> b: " << b << "\n\n";
    
        // Resize b.
        my_vec_resize(b, 4);
        std::cout << "Post resize --> b: " << b << "\n\n";
    
        // Check whether a == b.
        std::cout << "Check equality (a == b) --> " 
            << a << (a == b ? " == " : " != ") << b << "\n\n";
    
        // Try assignment (a is target)
        my_vec_assign(b, a);
        std::cout << "Post assignment my_vec_assign(b, a) --> a: " << a << "\n\n";
    
        // Check whether a == b.
        std::cout << "Check equality (a == b) --> " 
            << a << (a == b ? " == " : " != ") << b << "\n\n";
    
        // IMPORTANT: Don't leak memory.
        my_vec_destruct(a);
        my_vec_destruct(b);
        std::cout << "Post destruct --> a: " << a << ", b: " << b << "\n\n";
        return 0;
    }
    // end file: main.cpp
    

    Output

    Post construction --> a: [0,0]
    
    Post assign elements --> a: [7,11]
    
    Clone --> b: [7,11]
    
    Post resize --> b: [7,11,0,0]
    
    Check equality (a == b) --> [7,11] != [7,11,0,0]
    
    Post assignment my_vec_assign(b, a) --> a: [7,11,0,0]
    
    Check equality (a == b) --> [7,11,0,0] == [7,11,0,0]
    
    Post destruct --> a: [], b: []