Solved

store objects of a single class in an array and then write them all out to text file in plain text (not binary)

Posted on 2003-11-15
14
370 Views
Last Modified: 2010-04-02
I want to store all of these "customer" objects in an array (an array of pointers to the objects seems the wise choice to me).  Then, once all are stored and the user selects exit, my console program writes them all out at once.  Idea is that the ofstream should only be initialized once so that it does not open a close a file each time it writes an object, but instead writes every object that is a member of the class out at once.  I've looked at this for 10+ hours, tried it with static/non static, tried it with and without pointers, tried stripping away details, tried using this pointer, tried calling customerChoices with this->customerChoices, tried lots of variations on the theme, but...obviously, didn't work right.  If I called writeout via this->writeout, or perhaps even without this, and then did ofile << *this->firstname, I could get it to write out that customer's name.  In an effort to create all customers in the same manner, I made createnew() static so I could call it from main and create the first customer with it.  Then I loop and can create more customers.  Doing this, I can still write out the most recent customer inputted with createnew, but I cannot loop through the array to access each customer's info separately, as I get "the instruction at "xxxxxxx" referenced memory at "yyyyy" - the memory could not be 'read'.  Any lifesavers out there feel like helping somebody learn?  Thanks much,  email me at markdani3@hotmail.com if you want to see the other files that go with this header...

using namespace std;

class customer{
private:
      string* firstname, *lastname, *pin;
      account* myAccount;
      static customer* cust_array[];
      static int num_customers;
      int custid;

public:            

      class writeout_ofileopen                              //two exception classes
      {};
      class writeout_ofilewrite
      {};
///////////////////////////////////////////////////////////////////////////constructors

customer(string c_fnameIn, string c_lnameIn, string c_pinIn, double c_balanceIn) : firstname(c_fnameIn), lastname(c_lnameIn), pin(c_pinIn)
      {
            cout << "\n\ncreated customer with first name: " << firstname << "\nlast name:  " << lastname << "\npin:  " << pin;
            cout << "\nnum_customers: " << num_customers;  num_customers++; cout << "\nnum_customers after inc: " << num_customers;
            myAccount = new account(c_balanceIn);
            custid = num_customers; cout << "\ncustomer id: " << custid;
            customerChoices();
      }
      
      /*customer(customer& c) : firstname(c.firstname), lastname(c.lastname), pin(c.pin), myAccount(c.myAccount)
      {
            
            cout << "\n\ncreation by copy constructor, unexpected?...\n";
      }
      customer()
      {cout << "\n\n\ndefault constructor...is this wrong?";} */
////////////////////////////////////////////////////////////////////////////////////////////end constructors
//choose to w/draw, deposit, checkbalance, or exit..exiting writes out to customer_account.txt

double getdouble();               //verifies that input is a double

void customerChoices()                        {
            while(true){
            cout << "\nwhatcha gonna do?"
            << "\n'a' to add a customer"
            << "\n'd' to deposit"
            << "\n'w' to withdraw"
            << "\n'c' to check balance"
            << "\n'x' to exit"
            << "\ntype your choice and press enter:";
                  char choice;
                  cin >> choice;
                        switch(choice){
                                                
                                                case 'a' :
                                                {
                                                createnew();  break;
                                                }
                                                case 'd' :
                                                {
                                                int idnum; cout << "enter the id num to depo for:";
                                                cin >> idnum;
                                                cout << "\nhow much you want to depo?";
                                                double amount = getdouble();
                                                for(int j = 0; j<num_customers; j++)
                                                            {
                                                            if(cust_array[j]->custid == idnum)
                                                                  {      cust_array[j]->myAccount->deposit(amount);  break;      }
                                                            }                  
                                                            break;
                                                }

                              case 'w' :      
                                                {            
                                                cout << "\nhow much you want to withdraw?";
                                                double subtract = getdouble();
                                                myAccount->withdraw(subtract);      break;
                                                }
                                    
                              case 'c' :      {                        //brackets mandatory for initialization of tempbal...
                                                double tempbal = myAccount->getbalance();
                                                cout << "\nbalance is: " << tempbal;      break;
                                                }

                              case 'x' : {
                                    
                                    
                                    
                                    
                                    writeout();
                                    
                                    
                                    
                                    
                                    
                                    cout << "\n\n\nfind a file called customer_account.txt to view your information.\n";
                                    exit(1);
                                             }
                              
                              default : cout << "bad selection, try again.\n"; break;

                        }      //end switch
            }                                          
      }      //end while      
            //end customerChoices()                                    

////////////////////////////////////////////////////////////////////////////////////////
static void createnew()
{
string firstname1, lastname1, pin1;
cout << "enter your first name:";
getline(cin, firstname1);
cout << "enter your last name:";
getline(cin, lastname1);
cout << "enter your pin:";
getline(cin, pin1);

double getdoubleTemp();
cout << "enter your balance:";
double balance = getdoubleTemp();      


customer::cust_array[num_customers] = new customer(firstname1, lastname1, pin1, balance);  //supposed to insert pointer to new customer into array at current index level.
}
//////////////////////////////////////////////////////////////////////////////////////////



void writeout()
{
ofstream ofile("customer_account.txt", ios::trunc);
for(int j=0; j<customer::num_customers; j++)              //this is where bombs, trying to access memory that hasn't been allocated, i think?!
ofile << cust_array[j]->lastname << "\n";
}
      





};
///////////////////////////////////////////////////////////////////////////////end class
0
Comment
Question by:vizionary
  • 6
  • 4
  • 4
14 Comments
 
LVL 4

Expert Comment

by:n_fortynine
ID: 9757580
Your private members, which are supposedly arrays of strings:
>>string* firstname, *lastname, *pin;
are inconsistent with how you assign values to them:
>>customer(string c_fnameIn, string c_lnameIn, string c_pinIn, double c_balanceIn) : firstname(c_fnameIn), lastname
>>(c_lnameIn), pin(c_pinIn)
IF this is a profile for ONE customer, I suggest using
 string firstname, lastname, pin;
instead of pointers.
Otherwise you should re-design your constructor so that it allocates memory for these arrays of strings and then extracts inputs for these names and pins.

0
 
LVL 17

Expert Comment

by:rstaveley
ID: 9757760
n_fortynine is right, but note that the "otherwise" decision would be a bad design decision.
0
 

Author Comment

by:vizionary
ID: 9758623
I posted the wrong constructor - I had the constructor as customer(string* c_fnameIn, string* c_lnameIn, string* c_pinIn, double c_balanceIn)  -->  that much I can get write without a hassle.  But storing pointers to created customers in an array of pointers to customers and then writing them out is giving me a huge headache...
0
 
LVL 4

Expert Comment

by:n_fortynine
ID: 9759858
OK, so here's what I think is the problem. You have a static customer* cust_array[]; which is an array of pointers supposedly. But this array was never initialized. So this line:
>>customer::cust_array[num_customers] = new customer(firstname1, lastname1, pin1, balance);  //supposed to insert
>>pointer to new customer into array at current index level.
wouldn't make sense.

Consider a simple example:
int **arr;
that does not have the effect of:
arr[0] ->
arr[1] ->
...
arr[size - 1] ->
until you do:
arr = new int*[size];

Notice that it's not your individual customer instances that harassed you. It's your cust_array.
So at the beginning you should do cust_array = new customer*[size];
or something.
0
 

Author Comment

by:vizionary
ID: 9760822
In main, I did have customer* customer::cust_array[50];  --  initializing an array of 50 pointers to customers....n_fortynine, would you mind taking a look at the whole program, if I can manage to post it all here - 500 lines or so split across 4 or 5 files.  Maybe you could get it to load data into the array and read it back out correctly?....Or maybe you just have other suggestions about possible missteps?  Thanks again....
0
 
LVL 4

Expert Comment

by:n_fortynine
ID: 9761781
I will try to go through and see if there's any problem if you post your code. Tomorrow's Monday so probably there would be other experts looking into it as well if I can't manage my time to do so.
0
 

Author Comment

by:vizionary
ID: 9765510
OK - here is the full code as it sits.  This will write out only the most recently created customer object's information.  But, when I try to use the array "customer* customer::cust_array[50];", I get lost and get memory access errors.  For the writeout() function, I try loading the array with "cust_array[num_customers] = new customer(fnameptr, lnameptr, pinptr, balance);" and then looping through it to write each customer's info.  Can anyone tell me a good way to do what I'm trying to do given the setup shown below?  I'll give 250 to n_fortynine for the help so far, and an additional 1,000 to whoever can illustrate for me the working solution.  Thanks much....

MAIN FILE-------------------------------------------------------------------------------------

//            exercises/implements customer & account classes.
//            1.  asks customer for identifying information
//            2.  creates customer object with that info
//            3.  creation of customer object necessarily includes creation of account object with user-chosen balance
//            4.  gives execution control to customer/user for withdrawing from, depositing to, or checking balance of his/her account
//
//
/////////////////////////////////////////////////////////////////////////////////////////////////////////////////////

#include <iostream>
#include <string>
#include <fstream>
#include "account.h"
#include "IO.h"                  //this includes customer.h

using namespace std;

customer* customer::cust_array[50];
int account::number_accounts = 0;
int customer::num_customers = 0;
int main()
{
customer::createnew();

return 0;
}
//////////////////////////////////////////////////////////double checker helper function
double getdoubleTemp()            //there are 2 versions of this function doing the same thing at the moment...
{
  double d;
  string amount;
  char* pc;

  while ( 1) {

    cin >> amount;

    d = strtod ( amount.c_str(), &pc);
      
      if(!(!*pc)) cout << "\ntry a number.\n" << "\ntry again: ";
      
    if (( !*pc) && (d > 0)) break; // conversion successful, a valid double
   
    if(d<=0) cout << "\nnumber must be greater than 0.\n" << "\ntry again: ";
   
  }

  return d;
}


CUSTOMER.H   ------------------------------------         HEADER FILE  --FEEL FREE TO IGNORE THE DEFAULT AND COPY CONSTRUCTORS

//      customer class - 3 constructors
//      members -  pointer to myAccount, firstname, lastname, pin
//      member methods - customerChoices(), writeout(),
//      
//
//
/////////////////////////////////////////////////////////////////

using namespace std;

class customer{
private:
      string* firstname, * lastname, * pin;
      account* myAccount;
      static int num_customers;
      static customer* cust_array[];
      
public:            

      class writeout_ofileopen                              //two exception classes
      {};
      class writeout_ofilewrite
      {};
///////////////////////////////////////////////////////////////////////////constructors

customer(string* c_fnameIn, string* c_lnameIn, string* c_pinIn, double c_balanceIn) : firstname(c_fnameIn), lastname(c_lnameIn), pin(c_pinIn)
      {
            cout << "\n\ncreated customer with first name: " << *firstname << "\nlast name:  " << *lastname << "\npin:  " << *pin;
            num_customers++;
            myAccount = new account(c_balanceIn);
            
            this->customerChoices();
      }
      customer(customer& c) : firstname(c.firstname), lastname(c.lastname), pin(c.pin), myAccount(c.myAccount)
      {
            
            cout << "\n\ncreation by copy constructor, unexpected?...\n";
      }
      customer()
      {cout << "\n\n\ndefault constructor...unexpected?";}
////////////////////////////////////////////////////////////////////////////////////////////end constructors

double getdouble();
            
void writeout();            //writes out customer's private information and customer's associated account's balance

void customerChoices()      //choose to w/draw, deposit, checkbalance, or exit..exiting writes out to customer_account.txt                  {
            {
            while(true){
            cout << "\nwhatcha gonna do?"
            << "\n'a' to add another customer"
            << "\n'd' to deposit"
            << "\n'w' to withdraw"
            << "\n'c' to check balance"
            << "\n'x' to exit"
            << "\ntype your choice and press enter:";
                  char choice;
                  cin >> choice;
                  
                        switch(choice){
                                                {
                                                case 'a' : createnew();
                                                }
                                                
                                                
                                                case 'd' :
                                                {
                                                cout << "\nhow much you want to depo?";
                                                double amount = getdouble();
                                                myAccount->deposit(amount);                  break;
                                                }

                              case 'w' :      
                                                {            
                                                cout << "\nhow much you want to withdraw?";
                                                double subtract = getdouble();
                                                myAccount->withdraw(subtract);      break;
                                                }
                                    
                              case 'c' :      {                        //brackets mandatory for initialization of tempbal...
                                                double tempbal = myAccount->getbalance();
                                                cout << "\nbalance is: " << tempbal;      break;
                                                }

                              case 'x' : {
                                    this->writeout();
                                    cout << "\n\n\nfind a file called customer_accounts.txt to view your information.\n";
                                    exit(1);
                                             }
                              
                              default : cout << "bad selection, try again.\n"; break;

                        }                              //end switch
            }                                          //end while
      }                                                //end customerChoices()
///////////////////////////////////////////////////////////////////////////////////////////////////////////

static void createnew()                              //used to create new customer at start of and in the middle of execution
{
string firstname, lastname, pin;
cout << "enter your first name:";
getline(cin, firstname);
cout << "enter your last name:";
getline(cin, lastname);
cout << "enter your pin:";
getline(cin, pin);


double getdoubleTemp();//////////////////////////////////////////2-versions of function, should be rewritten currentversion is                                                                                                 //getdoubleTemp()            and customer::getdouble()
cout << "enter your balance:";
double balance = getdoubleTemp();      
////////////////////////////////ensures balance entered isn't zero, negative, or a letter.
//quick hack to reuse code - should be able to use the same function for the class data and this initial data.


string* fnameptr, *lnameptr, *pinptr;
fnameptr = &firstname;
lnameptr = &lastname;
pinptr = &pin;


cust_array[num_customers] = new customer(fnameptr, lnameptr, pinptr, balance);
}                                                      //end createnew()

};                                                      //end class


ACCOUNT.H--------------------------------------------------------------------HEADER FILE #2

//            account class - accounts are created by customers
//            members - balance
//            member functions - template deposit(), template withdraw(), getbalance(), setbalance()  -- setbalance not really needed
//
///////////////////////////////////////////////////////////////////////////////////////////////////////

using namespace std;

class account{
private:
      double balance;

public:
      static int number_accounts;
      account(double balanceIn)
      {
            balance = balanceIn;
            cout << "\nalso constructed account with balance:  " << balance;
            number_accounts++;
            cout << "\nNumber accounts = " << number_accounts;
      }
      
/////////////////////////////////////////////////////////////      depo/withdraw      
      template <class template_type>
            template_type deposit(template_type add)
      {      
            balance += add;
            cout << "\n\n **** UPDATED BALANCE ****\n"
            << " *** " << balance << " ***\n";
            return 1;
      }
      
      template <class template_type2>
      template_type2 withdraw(template_type2 subtract)
      {      
            balance -= subtract;
            cout << "\n\n **** UPDATED BALANCE ****\n"
            << " *** " << balance << " ***\n";      
            return 1;
      }

/////////////////////////////////////////////////////////      balance
      double getbalance()
      {      return balance;      }

      /*void setbalance(double setme)
      {      balance = setme;      }*/                  //not needed
};

///end class



ALMOST DONE ---------------------------IO.H-------------------------------HEADER FILE

//  getdouble():  error-checking (for data type) when deposit or withdrawal occurs, disallows text and negative number entry
//      writeout():  writes out text file containing customer/account data, errors if unable to open file or unable to write to
//                        file already opened.
//
/////////////////////////////////////////////////////////////////////////////
//stdlib.h - supposed to be necessary?
#include "customer.h"

double customer::getdouble()
{
  double d;
  string amount;
  char* pc;

  while ( 1) {

    cin >> amount;

    d = strtod ( amount.c_str(), &pc);
      
      if(!(!*pc)) cout << "\ntry a number.\n" << "\ntry again: ";
      
    if (( !*pc) && (d > 0)) break; // conversion successful, a valid double
   
    if(d<=0) cout << "\nnumber must be greater than 0.\n" << "\ntry again: ";
   
  }

  return d;
}


//////////////////////////////////////////////////////////////////////////////////


void customer::writeout()
{
ofstream ofile("customer_accounts.txt", ios::trunc);
ofile
      << *this->lastname << "\n"
      << *this->pin << "\n"
      << myAccount->getbalance() << "\n";
      
      
}


0
How to run any project with ease

Manage projects of all sizes how you want. Great for personal to-do lists, project milestones, team priorities and launch plans.
- Combine task lists, docs, spreadsheets, and chat in one
- View and edit from mobile/offline
- Cut down on emails

 
LVL 4

Expert Comment

by:n_fortynine
ID: 9766904
vizionary:

I haven't had a chance to look through your code yet. But after a quick run I found out that in createnew(), the last line
>>cust_array[num_customers] = new customer(fnameptr, lnameptr, pinptr, balance);
was not reached for some reason. I will go back some time later today and see if I can figure it out.
0
 

Author Comment

by:vizionary
ID: 9767970
Thanks much - but I show that line definitely executing - if I comment it out, the program stops executing after the name, pin, and balance are put in.  Don't know why it would have done that...

-->  I set up an email account with this address          n_fortynine@techie.com   and the password n_fortynine49  and emailed my files to you exactly as a zipped attachment.  Your continued help is much appreciated...

You can access the email account at www.mail.com

fyi, I'm compiling in vc++6 without precompiled headers - not sure if the precompiled headers thing means anything...

Viz
0
 
LVL 17

Accepted Solution

by:
rstaveley earned 250 total points
ID: 9770099
Your main problem is that all of your code is executed from the customer class constructor. You've even got an instance of a second customer being instantiated from the first customer's constructor via customerChoices and then createnew. Ouch. You should allow yourself to return to calling code, when the class instance is constructed and then do your processing on a constructed instance of the class.

e.g. Not ideally but better...

  static void createnew()
  {
    // ....

    customer *pcustomer = new customer(fnameptr, lnameptr, pinptr, balance); /* Constructor should not call customerChoices */
    p->customerChoices(); /* Need to reimplement this too do that creatnew isn't called from customerChoices. Otherwise you recurse */
    cust_array[num_customers] = pcustomer;

  }   //end createnew()

If you have createChoices working as a non-static class member rather than being passed a pointer to the instance of the class, you shouldn't have it call createnew() to create another class instance, unless you have that instance returned to calling code to allow yourself to return from the existing customer instance and thus get yourself out of recursion.

I recommend that you don't make customerChoices a non-static member at all. Make it a static member, which is passed a pointer to the existing customer instance. More normally you'd see customerChoices as a non-class function, which is passed a pointer or reference to the existing customer instance, using public accessors to set/get members; that would decouple your user interface from your customer class, which is good practice.

The std::string pointers are yucky, as previously mentioned, too. The std::string class dynamically allocates a buffer to store its payload and its destructor automatically cleans up. There is no real justification for using a std::string pointers in your class.
0
 
LVL 4

Assisted Solution

by:n_fortynine
n_fortynine earned 250 total points
ID: 9771676
rstaveley:

I looked at the problem last night and realized that his program is not returning from the ("recursive") calls when he exits the program using exit(1), making the constructor somewhat unfinished. I tried made the following changes so that it would.

In customerChoices():
 in case 'a': I added a return after createNew();
 in case 'x': I replaced exit(1) with return;

The functions are returned OK, yet here's my question: For cust_array, it is not possible to try and construct the next element even when the previous element has not finished being constructed (technically), is it? I think that's where his problem is.

vizionary:

Sorry I could not get back to you since yesterday. I have tried to find a solution without altering the structure of your program but I couldn't. I also recommend that you don't incorporate inline functions that uses static member variables. It's usually unsafe. Look up C++ Faqs (Cline, Lomow, Girou) section 16.04.

0
 
LVL 17

Expert Comment

by:rstaveley
ID: 9771873
>  For cust_array, it is not possible to try and construct the next element even when the previous element has not finished being constructed (technically), is it?

Technically, it would be practicable, but impractical :-) You really wouldn't want to do that.

It is wise to extract the user interface from the class for good design anyhow.
0
 
LVL 4

Expert Comment

by:n_fortynine
ID: 9772438
>>Technically, it would be practicable, but impractical
The functions returned safely after I made the modifications mentioned above. Yet when cust_array is accessed an error would occur. That's why I came up with the conclusion that in this case it is not possible to construct the next element within the processing of constructing the current element.

rstaveley, I wonder if there would happen to be another explanation for this (other than that I mentioned)?
0
 
LVL 17

Expert Comment

by:rstaveley
ID: 9773839
It really is an impractical design. You've got a bug which a tennis player might have described describe as a "forced error". With a more practical design you'd have less of a daunting task debugging.

Your access error is a consequence of the fact that createnew has a bunch of strings which are declared as automatic variables. That means that they are destroyed when createnew goes out of scope. However, their addresses are used to initialise those horrible string pointers in customer. The string pointers are therefore initialised with pointers to strings formerly instantiated on the stack which were deallocated as soon as createnew goes out of scope. When you subsequently try to dereference those pointers you get pointers to memory which has subsequently been initialised for something quite different.

I say: Sort the design out rather than trying to get the code to work with the existing design.

Step 1: Get rid of the string* class members.
Step 2: Separate the UI from the class.
....

[Sorry if I sound bombastic :-) ]
0

Featured Post

What Is Threat Intelligence?

Threat intelligence is often discussed, but rarely understood. Starting with a precise definition, along with clear business goals, is essential.

Join & Write a Comment

Suggested Solutions

Article by: SunnyDark
This article's goal is to present you with an easy to use XML wrapper for C++ and also present some interesting techniques that you might use with MS C++. The reason I built this class is to ease the pain of using XML files with C++, since there is…
Basic understanding on "OO- Object Orientation" is needed for designing a logical solution to solve a problem. Basic OOAD is a prerequisite for a coder to ensure that they follow the basic design of OO. This would help developers to understand the b…
The goal of the video will be to teach the user the difference and consequence of passing data by value vs passing data by reference in C++. An example of passing data by value as well as an example of passing data by reference will be be given. Bot…
The viewer will learn additional member functions of the vector class. Specifically, the capacity and swap member functions will be introduced.

706 members asked questions and received personalized solutions in the past 7 days.

Join the community of 500,000 technology professionals and ask your questions.

Join & Ask a Question

Need Help in Real-Time?

Connect with top rated Experts

18 Experts available now in Live!

Get 1:1 Help Now