0

Why does this code result in the second element of the array being printed as 0, irrespective of the value that was specified in the definition of the integer array object? The output of the code below is 7 0 3 4 5 6 instead of 7 2 3 4 5 6, what would be the reason for this behavior?

// Overloading operators for Array class
#include<iostream>
#include<cstdlib>

using namespace std;

// A class to represent an integer array
class Array
{
private:
    int *ptr;
    int size;
public:
    Array(int *, int);

    // Overloading [] operator to access elements in array style
    int &operator[] (int);

    // Utility function to print contents
    void print() const;
};

// Implementation of [] operator.  This function must return a
// reference as array element can be put on left side
int &Array::operator[](int index)
{
    if (index >= size)
    {
        cout << "Array index out of bound, exiting";
        exit(0);
    }
    return ptr[index];
}

// constructor for array class
Array::Array(int *p = NULL, int s = 0)
{
    size = s;
    ptr = NULL;
    if (s != 0)
    {
        ptr = new int[s];
        for (int i = 0; i < s; i++)
            ptr[i] = p[i];
        delete ptr;
    }
}

void Array::print() const
{
    for(int i = 0; i < size; i++)
        cout<<ptr[i]<<" ";
    cout<<endl;
}

// Driver program to test above methods
int main()
{
    int a[] = {1, 2, 3, 4, 5, 6};
    Array arr1(a, 6);
    arr1[0] = 7;
    arr1.print();
    arr1[8] = 6;
    return 0;
}
  • 1
    user4581301 told you the reason - your constructor has undefined behavior. But on a side note, don't call `exit()` for out of bounds access, throw an exception instead, like `std::out_of_range`. And you should also handle the case where `index` is less than 0. Also, since you are implementing a constructor and destructor, you should also implement assignment operators too, per the [Rule of 3/5/0](https://en.cppreference.com/w/cpp/language/rule_of_three). In this situation, you really should be using `std::vector` instead. – Remy Lebeau Feb 12 '20 at 01:13
  • How would one throw the std::out_of_range exception without getting the 'Aborted (core dumped)' message. – Darnoc Eloc Feb 12 '20 at 03:30
  • `catch` the exception before it exits `main()` – Remy Lebeau Feb 12 '20 at 06:22

3 Answers3

3

In the Array constructor, immediately after allocating and filling the dynamically allocated buffer at ptr, the buffer is released with

delete ptr;

All accesses of the buffer at ptr after this point invoke undefined behaviour. Side note: This should have been delete[] ptr; to ensure that the array was released correctly.

The solution: Don't do that!

Add a destructor to free ptr when Array goes out of scope and is done with the buffer.

// destructor for array class
Array::~Array()
{
    delete[] ptr;
}

The compiler will automatically generate a destructor for you, but that generic destructor is not qualified to know whether or not it is safe to delete[] what's at a pointer member. It might not be an array, the allocation could be owned by another object (See What is ownership of resources or pointers?) or perhaps not allocated dynamically with new.

This brings up a side note: The default special member functions that handle copying this object will mindlessly copy the pointer, not the allocation, and leave you with two objects pointing to the same allocation. Sooner or later this will be fatal because one copy will go out of scope before the other and if nothing else tries to access the freed allocation and break the program, the second delete[] will break the program. This issue and its solution is covered in detail at What is The Rule of Three?

The general rule of thumb is to not make a class like this and instead use std::vector. std::vector does all of this and a whole lot more.

user4581301
  • 29,019
  • 5
  • 26
  • 45
  • Not only that, but the OP was using `delete` instead of `delete[]` and thus is a secondary cause of undefined behavior. – Remy Lebeau Feb 12 '20 at 01:08
  • Is it necessary to explicitly define the destructor method? Deletion of the "delete ptr;" line would suffice, correct? – Darnoc Eloc Feb 12 '20 at 01:15
  • @DarnocEloc The constructor will generate a simple destructor for you, but this simple destructor will not release the memory because there are many cases where you do not want to free what's at a pointer. It might not have been dynamically allocated, for one thing, and it might not be this object's allocation to delete. – user4581301 Feb 12 '20 at 01:18
0

I modified the code as such to include an explicit default constructor and copy constructor, also included the std::out_of_range exception but am not sure if the latter is properly implemented. This was done as an exercise in handling arrays without utilizing the vector container from STL. Added a swap member function and assignment operator but getting a few error msgs.

class "Array" has no member "swap" member "Array::size" (declared at line 12) is inaccessible 'operator=' must be a member function 'this' may only be used inside a nonstatic member function

// Overloading operators for Array class
#include<iostream>
#include<cstdlib>
//#include<vector>

using namespace std;

// A class to represent an integer array
class Array{
private:
    int *ptr;
    int size;
public:
    Array(int *, int);
    Array(const Array&);
    ~Array();
    Array& operator= (Array);

    // Overloading [] operator to access elements in array style
    int &operator[] (int);

    // Utility function to print contents
    void print() const;

    friend void swap(Array& first, Array& second);};


// Implementation of [] operator.  This function must return a
// reference as array element can be put on left side
int &Array::operator[](int index){
    // try {
    //     return ptr[index];}
    // catch(const out_of_range& oor){
    //         cerr << "Out of Range error: " << oor.what() << '\n';}    
    if (index >= size || index < 0){
       throw out_of_range("Index out of Range error");
    }
    return ptr[index];
}

// constructor for array class
Array::Array(int *p = NULL, int s = 0){
    size = s;
    ptr = NULL;
    if (s != 0){
        ptr = new int[s];
        for (int i = 0; i < s; i++)
            ptr[i] = p[i];}
}

// destructor for array class
Array::~Array(){
    delete[] ptr;
    ptr = NULL;}

// copy constructor for array class
Array::Array(const Array& A) { 
    size = A.size;
    ptr  = new int[size];
    for (int i = 0; i < size; i++)
        ptr[i] = A.ptr[i];}

void Array::swap(Array& first, Array& second){
    using std::swap;
    swap(first.size, second.size);
    swap(first.ptr, second.ptr);}

//Assignment operator for array class
Array::Array& operator=(Array other){
    swap(*this, other); 
    return *this;}

//print function for array elements
void Array::print() const{
    cout << "{";
    for(int i = 0; i < size; i++)
        cout<<ptr[i]<<" ";
    cout<<"}"<<endl;}

// Driver program to test above methods
int main()
{
    int a[] = {1, 2, 3, 4, 5, 6};
    Array arr1(a, 6);
    arr1[0] = 7;
    arr1.print();
    Array arr2 = arr1;
    arr2.print();
    arr1[-1] = 4;
    return 0;
} 
  • 1
    Your `operator[]` should not be `catch`'ing the exception it `throw`s. That defeats the whole purpose of throwing an exception, and it is allowing `return ptr[index];` to be called with an invalid `index`. – Remy Lebeau Feb 12 '20 at 06:26
  • I replaced the try catch block with just a throw out of range statement. – Darnoc Eloc Feb 12 '20 at 14:39
  • Copy constructor looks good. An `operator=` is highly recommended to fill in one last potential bug that will hit when assignment takes place. The [Copy and Swap Idiom](https://stackoverflow.com/questions/3279543/what-is-the-copy-and-swap-idiom) provides a simple and effective way to use the copy constructor to do the heavy lifting for `=`. – user4581301 Feb 12 '20 at 18:15
  • New thought: Once you are happy with your array class, and if you have time, see what running it through [Code Review](https://codereview.stackexchange.com/help/asking) turns up. Bring thick skin. The reviewers are polite, but something about people combing over your code and finding things that could be better always grates no matter how nice folks are about it. I've linked to the help page because the code requirements for a well-received question are, by necessity, fairly specific. – user4581301 Feb 12 '20 at 22:10
0

Modified print function to exclude space after last array element. Modified constructor method declaration to include initialized arguments. Added additional const version of index[ ] operator overload but don't think it's properly implemented or if it would actually be utilized.

#include<iostream>
#include<cstdlib>


// A class to represent an integer array
class Array{
private:
    int *ptr;
    std::size_t size;
public:
    Array(int *p = nullptr, std::size_t s = 0);
    Array(const Array&);
    ~Array();
    Array& operator= (Array);

    // Overloading [] operator to access elements in array style
    int &operator[] (std::size_t);

    int const& operator[](std::size_t) const;

    // Utility function to print contents
    void print() const;

    friend void swap(Array& first, Array& second);};


// Implementation of [] operator.  This function must return a
// reference as array element can be put on left side
int &Array::operator[](std::size_t index){
    puts("overload");   
    if (index >= size || index < 0){
       throw std::out_of_range("Index out of Range error");
    }
    return ptr[index];
}

int const& Array::operator[](std::size_t index) const{
    puts("const overload");
    if (index >= size || index < 0){
        throw std::out_of_range("Index out of Range error");
    }
    return ptr[index];
}


// constructor for array class
Array::Array(int *p, std::size_t s){
    size = s;
    ptr = nullptr;
    if (s != 0){
        ptr = new int[s];
        for (int i = 0; i < s; i++){
            ptr[i] = p[i];}
    }
}

// destructor for array class
Array::~Array(){
    delete[] ptr;
    ptr = nullptr;}

// copy constructor for array class
Array::Array(const Array& A) { 
    size = A.size;
    ptr  = new int[size];
    for (int i = 0; i < size; i++){
        ptr[i] = A.ptr[i];}
}
//swap friend function of assignment operator
void swap(Array& first, Array& second){
    using std::swap;
    swap(first.size, second.size);
    swap(first.ptr, second.ptr);}

//Assignment operator for array class
Array& Array::operator=(Array other){
    swap(*this, other); 
    return *this;}

//print function for array elements
void Array::print() const{
    std::cout << "{";
    for(int i = 0; i < size; i++){
        std::cout << ptr[i];
        if (i == size-1){
            continue;}
        std::cout<<" ";       
       }
    std::cout<<"}"<< std::endl;}

// Driver program to test above methods
int main()
{
    int a[] = {1, 2, 3, 4, 5, 6};
    Array arr1(a, 6);
    std::cout << arr1[3] << '\n';
    arr1[4] = 7;
    arr1.print();
    Array arr2 = arr1;
    arr2.print();
    arr1[-1] = 4;
    return 0;
}