Question

I'm working on a project that deals with creating two strings, a username and a password. The two elements make an object of an Account. In the main, there is an Array of Accounts that is initialized at 10. I have a Save & Quit option, which saves the Username on one line and the Password on the next in the same file. A pair of lines signifies another account.

My question is, how do you properly save the data from the Array of Accounts, then load the data from the previous Array of Accounts?

I get a std::bad_alloc memory error every time I try the loadAccounts() function. I've several different methods, but to no avail.

So far I've come up with this for saving the array (works just as it should so far) :

void saveAccounts(Account accs [], int numIndexes)
{
std::ofstream savefile("savedata.sav", std::ofstream::binary); // By re-initializing the file, the old contents are overwritten.
for (int i = 0; i < numIndexes; i++)
{
    savefile << accs[i].getUsername() << endl;
    savefile << accs[i].getPassword() << endl;
}
savefile.close();
}

As for my loading function I have :

Account* loadAccounts() // Load the data from the file to later print to make sure it works correctly.
{
cout << "LOADING ACCOUNTS!" << endl;
std::ifstream loadfile("savedata.sav", std::ifstream::binary);

Account * acc_arr; // The "Array" to be returned.
Account tmp_arr [10]; // The array to help the returned "Array."
acc_arr = tmp_arr; // Allowing the "Array" to be used and returned because of the actual array.
if (loadfile.is_open())
{
    int i = 0;
    while (loadfile.good())
    {
        cout << "Loadfile is good and creating Account " << i+1 << "." << endl; // For my own benefit to make sure the data being read is good and actually entering the loop.
        std::string user;
        std::getline(loadfile, user);
        std::string pass;
        std::getline(loadfile, pass);
        Account tmpAcc(user, pass);
        tmp_arr[i] = tmpAcc;
        ++i;
    }
    Account endAcc = Account(); // The default constructor sets Username to "NULL."
    tmp_arr[i] = endAcc;
}
loadfile.close();
cout << "ACCOUNTS LOADED SUCCESSFUL!" << endl;
return acc_arr;
}

I've gathered that I can return an array by using a pointer and an actual array to do that same, since an array can't actually be returned.

I try to use the returned array here, which I'm trying to "copy" over the loaded array to the Array that will actually be printed. Later, I'll print the array (acc_arr) to ensure that the loaded array was loaded successfully :

else if (selection == 'l' || selection == 'L')
{
Account * tmp_acc_arr = new Account [10];
    tmp_acc_arr = loadAccounts();
    _getch();
    for (size_t i = 0; i < size_t(10); i++)
    {
        if (tmp_acc_arr[i].getUsername() == "NULL")
        {
            break;
        }
        acc_arr[i] = tmp_acc_arr[i];
        cout << "Added Account " << i << " succesfully." << endl;
    }
}

The error is caused by this last block of code. I've checked to make sure the data copied correctly by using

EDIT: Awkward... by using an if statement to make sure the data within the tmp_acc_arr actually has data stored once it was returned and initialized in the main.

Was it helpful?

Solution

tmp_arr ist local in loadAccounts and on the stack. It will be invalid once loadAccounts() returns. Your return value is an invalid stack-pointer.

You could hand your pointer tmp_acc_arr to the function as an argument and fill it with the values from your file. You should also check for overflow or better use STL containers like std::vector.

edit


void loadAccounts(Account * acc_memory, std::allocator<Account> alloc, size_t acc_array_size) // Load the data from the file to later print to make sure it works correctly.
{
  Account *end_of_construction = acc_memory;
  try
  {
    cout << "LOADING ACCOUNTS!" << endl;
    std::ifstream loadfile("savedata.sav", std::ifstream::binary);
    if (loadfile.is_open() && loadfile.good())
    {
        size_t i = 0;
        for (size_t i=0; i<acc_array_size; ++i)
        {
          if (loadfile.good())
          {
            cout << "Loadfile is good and creating Account " << i+1 << "." << endl; // For my own benefit to make sure the data being read is good and actually entering the loop.
            std::string user, pass;
            std::getline(loadfile, user);
            if (loadfile.good())
            {
              std::getline(loadfile, pass);
              alloc.construct(end_of_construction++, user, pass);
            }
            else alloc.construct(end_of_construction++);
          }
          else alloc.construct(end_of_construction++);
        }
    }
    loadfile.close();
    cout << "ACCOUNTS LOADED SUCCESSFUL!" << endl;
  }
  catch (...)
  {
    size_t num_constructed = end_of_construction-acc_memory;
    for (size_t i=0; i<num_constructed; ++i) alloc.destroy(acc_memory + i);
    throw;
  }
}

Used like

  size_t const num_elements = 10;
  std::allocator<Account> acc_alloc;
  Account * tmp_acc_arr = acc_alloc.allocate(num_elements);
  loadAccounts(tmp_acc_arr, acc_alloc, num_elements);
  // do stuff
  for (size_t i=0; i<num_elements; ++i) acc_alloc.destroy(tmp_acc_arr + i);
  acc_alloc.deallocate(tmp_acc_arr, num_elements);

OTHER TIPS

You return a pointer that points to an array that is destroyed as soon as you exit the function. Using that pointer leads to undefined behavior, that is BAD.

As you observed, array is not a valid return type, so to return it actually you shall put it inside a struct, and return the struct.

Licensed under: CC-BY-SA with attribution
Not affiliated with StackOverflow
scroll top