On LearnCPP, I came across code that converts a variable of any given type to an std::string. There's also this warning there: "Note that this solution omits any error checking. It is possible that inserting tX into oStream could fail. An appropriate response would be to throw an exception if the conversion fails.". So I was wondering how to check for that case. Any suggestions, guys?
I expect because that exception isn't thrown, because this code segFaults before it gets there when you do this anArray[nCount] = nCount;
because m_pnData is a null pointer, having been set to the value 0 and never changed.
If I was reviewing this code, I would say that IntArray is what I technically call a "badly designed" class, because it's possible to create it in a state such that when someone tries to use it, it segFaults.
Like this: m_pnData = newint[m_nLength]
which is being done in the other constructor. Did you write this code? It seems odd that you know how to allocate some memory in a constructor, and at the same time not know how to do it.
I was having trouble actually correctly calling that constructor, I guess.
Anyway, since the IntArray class a vector-like class (sans template, though - template version was shown in another lesson) that I got from LearnCPP itself in an earlier lesson, using the same code from the original class works. But now it's working a bit too well and I don't know what type or variable to use to test the exception.
#include <iostream>
#include "IntArray.h"
#include "ArrayException.h"
IntArray::IntArray()
{
m_nLength = 0;
m_pnData = 0;
}
IntArray::IntArray(constint nLength)
:m_pnData{ newint[nLength] }, m_nLength{ nLength }
{
}
IntArray::~IntArray()
{
delete[] m_pnData;
}
void IntArray::Erase()
{
delete[] m_pnData;
m_pnData = 0;
m_nLength = 0;
}
int& IntArray::operator[](constint nIndex)
{
if (nIndex < 0 || nIndex >= m_nLength)
{
throw ArrayException{ "Invalid index" };
}
return m_pnData[nIndex];
}
int IntArray::GetLength() const
{
return m_nLength;
}
// Reallocate resizes the array. Any existing elements will be destroyed.
// This function operates quickly.
void IntArray::Reallocate(constint nNewLength)
{
// First we delete any existing elements
Erase();
// If our array is going to be empty now, return here
if (nNewLength <= 0)
{
return;
}
// Then we have to allocate new elements
m_pnData = newint[nNewLength];
m_nLength = nNewLength;
}
// Resize resizes the array. Any existing elements will be kept.
// This function operates slowly.
void IntArray::Resize(constint nNewLength)
{
// If we are resizing to an empty array, do that and return
if (nNewLength <= 0)
{
Erase();
return;
}
// Now we can assume nNewLength is at least 1 element. This algorithm
// works as follows: First we are going to allocate a new array. Then we
// are going to copy elements from the existing array to the new array.
// Once that is done, we can destroy the old array, and make m_pnData
// point to the new array.
// First we have to allocate a new array
int *pnData = newint[nNewLength];
// Then we have to figure out how many elements to copy from the existing
// array to the new array. We want to copy as many elements as there are
// in the smaller of the two arrays.
if (m_nLength > 0)
{
int nElementsToCopy = (nNewLength > m_nLength) ? m_nLength : nNewLength;
// Now copy the elements one by one
for (int nIndex = 0; nIndex < nElementsToCopy; nIndex++)
{
pnData[nIndex] = m_pnData[nIndex];
}
}
// Now we can delete the old array because we don't need it any more
delete[] m_pnData;
// And use the new array instead! Note that this simply makes m_pnData point
// to the same address as the new array we dynamically allocated. Because
// pnData was dynamically allocated, it won't be destroyed when it goes out of scope.
m_pnData = pnData;
m_nLength = nNewLength;
}
void IntArray::InsertBefore(constint nValue, constint nIndex)
{
// Sanity check our nIndex value
if (nIndex < 0 || nIndex >= m_nLength)
{
throw ArrayException{ "Invalid index" };
}
// First create a new array one element larger than the old array
int *pnData = newint[m_nLength + 1];
// Copy all of the elements up to the index
for (int nBefore = 0; nBefore < nIndex; nBefore++)
{
pnData[nBefore] = m_pnData[nBefore];
}
// Insert our new element into the new array
pnData[nIndex] = nValue;
// Copy all of the values after the inserted element
for (int nAfter = nIndex; nAfter < m_nLength; nAfter++)
{
pnData[nAfter + 1] = m_pnData[nAfter];
}
// Finally, delete the old array, and use the new array instead
delete[] m_pnData;
m_pnData = pnData;
m_nLength += 1;
}
void IntArray::Remove(constint nIndex)
{
// Sanity check our nIndex value
if (nIndex < 0 || nIndex >= m_nLength)
{
throw ArrayException{ "Invalid index" };
}
// First create a new array one element smaller than the old array
int *pnData = newint[m_nLength - 1];
// Copy all of the elements up to the index
for (int nBefore = 0; nBefore < nIndex; nBefore++)
{
pnData[nBefore] = m_pnData[nBefore];
}
// Copy all of the values after the removed element
for (int nAfter = nIndex + 1; nAfter < m_nLength; nAfter++)
{
pnData[nAfter - 1] = m_pnData[nAfter];
}
// Finally, delete the old array, and use the new array instead
delete[] m_pnData;
m_pnData = pnData;
m_nLength -= 1;
}
void IntArray::InsertAtBeginning(constint nValue)
{
InsertBefore(nValue, 0);
}
void IntArray::InsertAtEnd(constint nValue)
{
InsertBefore(nValue, m_nLength);
}
int IntArray::GetElement(constint nIndex) const
{
return m_pnData[nIndex];
}
std::ostream& operator<< (std::ostream &out, const IntArray &anArray)
{
out << anArray.m_pnData;
return out;
}