-2

/*Edit: I'm not sure if I did something wrong, but some of the comments and the downvotes are making me feel really dumb for asking a simple question.

I appreciate the constructive criticism as I am a complete coding noobie. For those of you who did help I really appreciate it and I did learn quite a bit.

I just wish some of you were less rude when it came to answering my question. Oh well, bring on the downvotes. */

Hi guys this is my first question here! I created a simple Array class that allows the user to create a dynamic 2D array with specified rows and cols. Everything works fine until the destructor is called and I try to delete the dynamically created data array.

I will supply my header and implementation file:

/* Array.h
 * Array Class Header File
 */

#ifndef ARRAY_H
#define ARRAY_H

class Array{
  private:
    int row;
    int col;
    int **data;
  public:
    Array(int,int);
    ~Array();
    int getRow() { return row;}
    int getCol() {return col;}
    int getData(int,int);

};

#endif




/* Array.cpp
 * Array implementation file
 */

#include <iostream>
#include <cstdlib>
#include "Array.h"

using namespace std;

//Constructor
Array::Array(int rowIn,int colIn){
// RowIn and ColIn must be greater than 1 to create matrix
  if(rowIn <= 1 || colIn <= 1){
    cout << "Row and column must be greater than 1" << endl;
    return;
  }
// If they are greater than 1 then set member variables
  row = rowIn;
  col = colIn;

// Allocate memory for 2D Array
  data = new int*[row];
  for(int i = 0; i < row; i++){
    data[i] = new int[col];
  }

// Fill 2D array with random numbers
  for(int i = 0; i < row; i++){
    for(int j = 0; j < col; j++){
      data[i][j] = rand()%90 + 10;
    }
  }

}

// Getter function that returns data at row and col
int Array::getData(int row,int col){
  return data[row][col];
}

//Destructor
Array::~Array(){
  for(int i = 0; i < row; i++){
    delete[] data[i];
  }

  delete []data;
}



/* Main.cpp
 * File to test array class
 * 
 */
#include <iostream>
#include <cstdlib>
#include <ctime>
#include "Array.h"

using namespace std;

void printArray(Array);

int main(){
  int t = time(0);
  srand(t);

  Array test(10,10);

  printArray(test);

  return 0;
}

void printArray(Array a){
  for(int i = 0; i < a.getRow(); i++){
    for(int j =0 ; j < a.getCol(); j++){
      cout << a.getData(i,j) << " ";
    }
    cout << endl;
  }
Biggem
  • 1
  • 1
  • There are no errors in the code bits above. Please provide [mcve]. Probably you break The Rule of Three. Also your destructor would try to delete not allocated memory in case if invalid arguments passed to the constructor. Instead of return it should throw an exception. – 273K Mar 31 '18 at 03:17
  • Please post how you're using this `Array` class, i.e. a `main` program. Also -- *Everything works fine until the destructor is called* -- Not true -- there is nothing wrong with the destructor, as it is doing the job it's supposed to do. The issue is that you're corrupting your object before the destructor is called. – PaulMcKenzie Mar 31 '18 at 03:18
  • I provided a main program for you to look at. I'm really not doing much. I'm just creating, printing, then destroying, I have no idea what I'm doing wrong. – Biggem Mar 31 '18 at 03:21
  • 1
    Well, your latest edit shows the error. `void printArray(Array a)` You are passing an `Array` by value, thus copying will occur. Your `Array` class is not safely copyable. Writing classes that have members as pointers to dynamically allocated memory isn't so simple now, is it? Read up on [the rule of 3/5/0](http://en.cppreference.com/w/cpp/language/rule_of_three) – PaulMcKenzie Mar 31 '18 at 03:22
  • Oh wow, you're right. I passed by reference and error is gone. However I'm not fully understanding why this is causing a problem? Aren't I just creating a copy to print and not actually changing the array object? – Biggem Mar 31 '18 at 03:26
  • There are too many problems with the shown code. The most difficult thing here is to figure out which particular breakage to discuss first. Violation of the Rule of Three? The fact that if invalid values get passed to the constructor, the destructor is going to start scribbling and corrupting innocent memory, that done absolutely nothing wrong? Where should someone start, with this... Let's start with the [Rule Of Three](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three). Please study this, and fix your class accordingly. Then we can proceed to the next problem. – Sam Varshavchik Mar 31 '18 at 03:26
  • @Biggem -- Your class lacks a user-defined copy constructor and assignment operator, instead the default versions of these functions will be used. If you want to see what happens -- change your code back to not using a reference, use your debugger and place a breakpoint in the destructor. You will see that the destructor is called more than once, using the same pointer value in the `delete` call. The links already given to you explain why that is the case, and why you want to prevent that from happening. – PaulMcKenzie Mar 31 '18 at 03:29
  • Sam what are the too many problems? I understand that I forgot to add a check in the destructor to see if memory was actually allocated. Which I believed I just fixed by checking if the data variable is null and if so, I return. I now just learned that I am copying my array in my print function which then deletes the allocated memory after the function call is over, and then the destructor attempts to delete that same memory after. What other problems am I having? – Biggem Mar 31 '18 at 03:30
  • I understand the default copy and assignment operators were causing problems. My question right now is whether or not I am causing a problem when I create array with the constructor while using values < 1. I failed to mention that in my constructor I set **data = nullptr. The reason I did this is because if a user tried to create an array with rows/cols < 1 then no memory would be allocated so the data ptr remains nullptr. Now in the destructor I check if the data ptr is nullptr, if it is I don't delete anything. Is this incorrect? – Biggem Mar 31 '18 at 03:42
  • @Biggem -- Your `Array`constructor that takes two arguments constructs an `Array` that has uninitialized values if the arguments passed are not correct. You just output a message and `return`. That does not stop the user from using this object, and it will cause havoc when you attempt to destroy the object. Usually a user-defined exception is thrown in those type of situations during construction, thus no object would be created. Second, there is no need to check for `nullptr` when issuing a `delete` call. Issuing a `delete` on a `nullptr` is perfectly valid. – PaulMcKenzie Mar 31 '18 at 03:44
  • Oh I see what you're saying. If arguments passed aren't correct I'm still creating an empty object essentially... What would be the correct way to deal with something like this? – Biggem Mar 31 '18 at 03:48
  • @Biggem -- Throw an exception. Then no object is created, and the user is responsible for handling the thrown exception. BTW, this entire way of creating a 2D array is very bad. The other major issue is that if one of those calls to `new[]` fail, how do you "rollback" all of the prior calls to `new` so that they can get deleted? For example if the Array is 100 x 100, and the 43rd call to `new` failed, how do you recover? If this is what is being taught, I would consider this, quite honestly, a bug in the teaching curriculum. – PaulMcKenzie Mar 31 '18 at 03:49
  • @PaulMcKenzie -- I am not familiar with "throwing an exception" I'm not too far along in my programming courses. I will do some research. Thank you. And yes this is how I was taught to create a dynamic 2D array. What other way would you suggest? Even the courses on YouTube suggest this way. – Biggem Mar 31 '18 at 03:53
  • @Biggem To create the 2D array in a less error prone manner, where the number of calls to `new` are reduced to just 2 calls, [see this example](https://stackoverflow.com/questions/21943621/how-to-create-a-contiguous-2d-array-in-c/21944048#21944048). Now, if a call to `new` fails, you only have a single call to `new` to rollback with a `delete`. The other issue with your way of allocating -- what if the array is 1000 x 1000? That is 1,001 calls to `new` and then 1,001 calls to `delete`. With the method I linked to, there are 2 calls to `new` and `delete`, regardless of the size of the Array – PaulMcKenzie Mar 31 '18 at 03:57
  • Possible duplicate of [What is The Rule of Three?](https://stackoverflow.com/questions/4172722/what-is-the-rule-of-three) – Miles Budnek Mar 31 '18 at 03:58
  • @PaulMcKenzie Thank you. You were very insightful and I appreciate all the constructive criticism. I have one more question for you before I let you go if that is alright: Why would a call to new fail and when what is a situation like this where that would happen? – Biggem Mar 31 '18 at 04:01
  • @Biggem -- If the allocation fails when using `new` (usually due to a lack of memory), then a `std::bad_alloc` exception is thrown. – PaulMcKenzie Mar 31 '18 at 04:02

2 Answers2

0

You need to declare your print function as void printArray(Array& a), otherwise, the array is copied when passed (default in C and C++ is pass-by-value = copying).

As a result, after the call to print, the copy releases all the pointers (correctly), and then the original does it again - the same data - which is undefined behavior.

Aganju
  • 6,295
  • 1
  • 12
  • 23
0

If you do not want to do a lot of things, delete the copying constructor and the assignment operator:

Array(const Array&) = delete;
const Array& operator= (const Array&) = delete;

And change both declaration and definition

void printArray(const Array &a);

Also be careful with the constructor. Current implementation leaves uninitialized object if 0 or 1 is passed to it via arguments and further destructor will abort.

273K
  • 29,503
  • 10
  • 41
  • 64