Rhuen Rhuen - 2 months ago 16
C++ Question

How can I store a string(from a file with n number of lines) in a dynamic array initialized as a unique pointer? C++

A student looking for some guidance...

This is a class assignment with the instructions:
Re-write your program, List of Chores, using a

unique_ptr
object as your data member. You should store a dynamic array in the
unique_ptr
object. Use this array for storing, retrieving, deleting and updating chores.

I am trying to learn more about using the
unique_ptr
object so I may create the constructor which is supposed to initialize the list. A text file has the list and the constructor should store that list into an array. I am trying to work around the error "Access violation reading location." In the original program, I created a temporary dynamic array with a larger capacity and copied the list into that array. There's a list of 10 chores in the text file. Here is that code:

In Header:

private:
/* var to keep len of list */
int len = 0;
int max = 9;
/* add appropriate data structure to store list */
string *arr = new string[max];


In .cpp:

/* reads the file line by line and initializes list */
ListOfChores::ListOfChores(string fileName){
ifstream file(fileName, ifstream::in);
string line;
if (file.is_open()) //Checking if the file can be opened
{
while (getline(file, line)) // Gets a single line
{
if (len >= max)
{
string *narr = new string[max + 10]; // New, larger array
for (int i = 0; i < max; i++)
{
narr[i] = arr[i]; // Copies line
}
delete[] arr; // Clears
arr = narr; // Copies
max += 1; // Growth
}
arr[len] = line; // Store a line in the array
len++; // Increases length by 1
}
file.close(); // Closes file
}
else cout << "Unable to open file" << endl;
}


And to show that I have been working on this and am not a lazy student...

New program attempt header:

private:
/* var to keep len of list */
int len = 0;
int max = 9;
/* add appropriate data structure to store list */
string *arr = new string[max]; // Primary array
string *narr = new string[max]; // New array


New program .cpp:

/* reads the file line by line and initializes list */
ListOfChores::ListOfChores(string fileName) {
unique_ptr<string[]> arr(new string[max]); // Unique pointer initialization

ifstream file(fileName, ifstream::in);
string line = " ";
if (file.is_open()) //Checking if the file can be opened
{
while (getline(file, line)) // Gets lines from file
{
if (len >= max)
{
max++; // Growth
unique_ptr<string[]> narr(new string[max]); // New unique pointer
narr = move(arr);// narr owns the object
narr[max] = line; // Store a line in the array
len++; // Increases length by 1
arr = move(narr); // arr owns the object
}
else
{
arr[len] = line; // Store a line in the array
len++; // Increases length by 1
}
}
file.close(); // Closes file
}
else cout << "Unable to open file" << endl;
}

Answer

The entire point of a unique_ptr is to destroy the managed pointer when the unique_ptr goes out of scope. This means you do not want it declared inside any confined scope. Any work you do will be destroyed as soon as the current block of code is exited. A unique_ptr created inside a function and not returned to the caller is gone and it's contents are gone with it as soon as the function returns. If you have a class intended to store a dynamic amount of data, then the data should be managed at the object's scope.

So

private:
    /* var to keep len of list */
    int len = 0;
    int max = 20; // made bigger so first 50% increase is 10 elements
    /* add appropriate data structure to store list */
    std::unique_ptr<std::string[]> arr; // Primary array
    // no need for New array here

Discussion of whether a 50% or 100% array size increase is better can be found here: What is the ideal growth rate for a dynamically allocated array?. As always, your mileage may vary, but a one-at-a-time increase is generally agreed upon to be a bad idea.

Now onto ListOfChores where we want to use, but not declare, the unique_ptr.

ListOfChores::ListOfChores(string fileName) {
    //no unique_ptr here. Scope is too narrow to be useful    
    ifstream file(fileName, ifstream::in);
    string line = " ";
    if (file.is_open()) //Checking if the file can be opened
    {
        while (getline(file, line)) // Gets lines from file
        {
            if (len >= max)// the current size is too small Let's make it bigger!
                           // if we grow before adding the line, we don't need any 
                           // special code to add the new line.
            {
                max *= 1.5; // Grow by 50%. Numerous studies have shown that 50% is a 
                            // good balance of RAM vs copy overhead in the general case
                std::string * narr = new string[max]; // no unique_ptr here either
                // old school copy for simplicity and obviousness
                for (int index = 0; index < len; index++)
                {
                     narr[index] = arr[index]
                } 
                arr.reset(narr); // frees and replaces the old array
                                 // arr now manages narr
            }
            // done growing, add normally to array 
            arr[len] = line; // Store a line in the array
            len++; // Increases length by 1
        }
        file.close(); // Closes file
    }
    else cout << "Unable to open file" << endl;
}

Other ListOfChores member functions will use arr, reading, adding and subtracting as needed. In order to add efficiently, the array growth code should be removed from the constructor and placed in a private method to be called by the constructor and other methods that need to enlarge the array.

Comments