Growing/Shrinking Dynamic Array

I wrote a function to help with a C++ challenge where growing/shrinking dynamic array's occurs frequently. So far the function seems to work fine, however, from my tests if someone does this
1
2
3
4
  int * ptr = new int[10];

  ptr[20]=9;
  cout<<ptr[20];


The value of 9 assigned to unallocated memory of index 20 will STILL be returned. I have tested that the copy & shrink works fine, thats easy enough, but how do I test that the array has actually been allocated properly when using a higher value? Here is my function code

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
template<typename TP>
bool GrowShrinkDynamicArray(TP * arr, int GrowShrinkBy, int origSize, bool copyData) {
	
	bool isSubtraction = false;
	int gsb = GrowShrinkBy;
	int newSize = 0;

	if(origSize < 0) { return false; }

	if(gsb < 0) {
		isSubtraction = true;
		gsb = gsb * -1;

	}

	if(origSize==0 && !isSubtraction) {
		try {
			arr = new TP[gsb];
		}
		catch(...) {
			return false;
		}

                return true;
	}else if(origSize==0 && isSubtraction) {
		return false;
	}

	if(origSize==1 && isSubtraction) {
		delete[] arr;
		return true;
	}

	if(copyData==false) {
		if(isSubtraction) {
			newSize = origSize - gsb;
		}else{
			newSize = origSize + gsb;
		}
		delete[] arr;
		try {
			arr = new TP[newSize];
		}
		catch(...) {
			return false;
		}
		return true;
	}else{
		int * aTemp;
		int iterSize = 0;

		if(isSubtraction) {
			newSize = origSize - gsb;
			iterSize = newSize;
		}else{
			newSize = origSize + gsb;
			iterSize = origSize;
		}

		try {
			aTemp = new TP[newSize];
		}
		catch(...) {
			return false;
		}
		
		for(int iter=0; iter<iterSize; iter++) {
			aTemp[iter] = arr[iter];
		}
		
		delete [] arr;
		
		try {
			arr = new TP[newSize];
		}
		catch (...) {
			delete [] aTemp;
			return false;
		}
		
		for(int cIter=0;cIter<iterSize;cIter++) {
			arr[cIter] = aTemp[cIter];
		}
		delete[] aTemp;
		return true;

	}
}


From what I can find, you can't really tell if much of a dynamic array, so the only solution is to make SURE your code is correct to prevent memory leaks and what not.

EDIT: Also out of curiousity, at the end of the copy where I do delete[] aTemp, is that line necessary since everything inside this function is destroyed once it exits?
Last edited on
From what I can find, you can't really tell if much of a dynamic array, so the only solution is to make SURE your code is correct to prevent memory leaks and what not.


Or use a type that has checked access, like std::vector and std::vector::at. It should be vanishing rare that you are managing your own dynamic memory.

Even without reading through your function, it is easy to see it is incorrect from the prototype. It receives a copy of a pointer and has no way of modifying the original, so all changes are lost when the function ends.
@cire:
here is my test of it in main
1
2
3
4
5
6
7
8
9
10
11
12
	int * ptr = new int[10];

	for (int i = 0; i<10; i++ ) {
		ptr[i]=i;
	}

	cout<<ptr[9]<<endl; // successfully returns 9

	if(GrowShrinkDynamicArray(ptr, -1, 10, true)) {
		cout<<ptr[8]<<endl; //returns 8 as expected
		cout<<ptr[9]<<endl; //returns some random large number, as expected since it no longer exists
	}


with the function as-is, it IS modifying ptr...

Edit: Also, the challenge explicitly says use dynamic arrays. It is from an EDU site and it does mention vectors, but says not to use them as the topic hasn't been covered yet.. to stick with the challenge, i use dynamic arrays
Last edited on
> as expected since it no longer exists
wrong, undefined behaviour is undefined. You don't expect `a high value', `zero', `unchanged', `-1' because you can't expect anything.

> with the function as-is, it IS modifying ptr...
It is not. Check the value of `ptr' before and after the function call.
Your function may delete the array, but ptr keep pointing there.
I'll suggest to print from a debugger.


Your function is unnecessarily complicated

Also, don't use exceptions as error codes (there is a nothrow version of new)
so how would I pass the actual array to the function, remove the * from TP * arr?
Here is what I have so far, with changes.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
template<typename TP>
bool GrowShrinkDynamicArray(TP arr[], int GrowShrinkBy, int origSize, bool copyData) {
	
	bool isSubtraction = false;
	int gsb = GrowShrinkBy;
	int newSize = 0;

	if(origSize < 0) { return false; }

	if(gsb < 0) {
		isSubtraction = true;
		gsb = gsb * -1;

	} else if(gsb==0) {
		return false;
	}

	if(origSize==0 && !isSubtraction) {
		arr = new (nothrow) TP[gsb];
		if(arr==0) { return false; }
		return true;

	}else if(origSize==0 && isSubtraction) {
		return false;
	}

	if(origSize==1 && isSubtraction) {
		delete[] arr;
		return true;
	}

	if(copyData==false) {
		if(isSubtraction) {
			newSize = origSize - gsb;
		}else{
			newSize = origSize + gsb;
		}
		delete[] arr;
		arr = new (nothrow) TP[newSize];

		if(arr==0) {return false;}

		return true;
	}else{
		int * aTemp;
		int iterSize = 0;

		if(isSubtraction) {
			newSize = origSize - gsb;
			iterSize = newSize;
		}else{
			newSize = origSize + gsb;
			iterSize = origSize;
		}

		aTemp = new (nothrow) TP[newSize];
		if(aTemp==0) { return false; }
		
		for(int iter=0; iter<iterSize; iter++) {
			aTemp[iter] = arr[iter];
		}
		
		delete [] arr;

		arr = new (nothrow) TP[newSize];
		if(arr==0) { delete [] aTemp; return false; }
		
		for(int cIter=0;cIter<iterSize;cIter++) {
			arr[cIter] = aTemp[cIter];
		}

		delete[] aTemp;
		return true;

	}
}


Replaced first parameter of function from TP * arr to TP arr[] and removed try/catches and replaced with (nothrow).

@ne555: how is it unnecessarily complicated?
Last edited on
You have superfluous special cases, and move the data twice.

> Replaced first parameter of function from TP * arr to TP arr[]
that made no difference.
bool GrowShrinkDynamicArray(TP *&arr
@ne555:

I was thinking about your previous comment about the ptr still pointing at the same address regardless of deleting the array.

The point of the function is to re-allocate memory for the array (free memory or take some). You said I >am< successfully deleting the array, therefore, freeing memory, correct? I then copy back over the required data plus or minus some, depending on if im sizing up or downward.

So based on that, shouldn't my function be successfully re-allocating memory?

For example

1
2
3
4
5
6
7
8
9
int * ptr = new int[10];

//some code that sets values for each index here

//now lets re-allocate the array to only hold 9 values instead of 10, copying over the already set 9 values in array.

if(GrowShrinkDynamicArray(ptr, -1, 10, true)) {
     cout<<"SUCCESS!"
}

//OR//
1
2
3
4
5
6
7
8
9
10
int * ptr = new int[10];

// some code that sets values for each index here

//grow the array, keeping already set values

if(GrowShrinkDynamicArray(ptr, 5, 10, true)) {
     cout<<"SUCCESS";
     //memory should now be allocated for indexes 10-14
}
TP * arr and TP arr[] are equivalent in the context of a parameter lists. Both are pointers which are passed by value.


bool GrowShrinkDynamicArray(TP*& arr, int GrowShrinkBy, int origSize, bool copyData) {

To convince yourself, run the following snippet:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
#include <iostream>

void passPtrByReference( int*& ptrByRef )
{
    ptrByRef = reinterpret_cast<int*>(1) ;
}

void passPtrByValue( int* ptrByVal )
{
    ptrByVal = reinterpret_cast<int*>(2) ;
}

int main()
{
    int * ptr = 0 ;
    std::cout << ptr << '\n' ;

    passPtrByReference(ptr) ;
    std::cout << ptr << '\n' ;

    passPtrByValue(ptr) ;
    std::cout << ptr << '\n' ;
}


http://ideone.com/ZR3Rdv

@ne555: how is it unnecessarily complicated?

How is it not?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
template <typename T>
bool reallocate( T*& mem, unsigned oldSize, unsigned newSize )
{
    if ( oldSize == newSize )
        return true ;

    try 
    {
        T* newMem = new T[newSize] ;
        std::copy(mem, mem+oldSize, newMem) ;
        delete [] mem ;
        mem = newMem ;
        return true ;
    }
    catch (...)
    {
        return false ;
    }
}


A "reallocate" without a copy is simply an allocation.

[Edit: Note that if copy throws, the memory allocated on the previous line will leak. I'll leave it up to you to deal with that.]
Last edited on
lol... didn't know about the std::copy, so now I see how it is unnecessarily complicated.

if i take lines 11-13 and place them after the try/catch, wouldn't that solve the case of copy throwing an error since everything in a function is tossed once you return from said function?
NO WAY! AN ONLINE C++ COMPILER!!! THANK YOU SO MUCH FOR THAT :D :D :D

Thank you for helping me thus far as well to better understanding managing dynamic arrays. I got over excited when I realized your link was an online C++ compiler.. didn't know anything of the sort existed. :D
if i take lines 11-13 and place them after the try/catch, wouldn't that solve the case of copy throwing an error since everything in a function is tossed once you return from said function?


Unfortunately, no. That wouldn't address the issue. The issue being if an assignment that occurs when std::copy is called throws, the memory pointed to by newMem would not be deallocated.

One possible solution would be:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
template <typename T>
bool reallocate( T*& mem, unsigned oldSize, unsigned newSize )
{
    if ( oldSize == newSize )
        return true ;

    T* newMem = nullptr ; // Define newMem at a broader scope

    try 
    {
        newMem = new T[newSize] ;
        std::copy(mem, mem+oldSize, newMem) ;
        delete [] mem ;
        mem = newMem ;
        return true ;
    }
    catch (...)
    {
        delete [] newMem ; // It is safe to call delete on a null pointer.
        return false ;
    }
}
Last edited on
Topic archived. No new replies allowed.