I'm writing a C++ program that reads in a list of passengers and creates Passenger objects that are stored in a dynamically allocated pointer. However, I noticed that when I ran the program, adding a new passenger would cause all of the previously set passengers to be assigned to the new passenger too.
The likely problematic segment of the code below:
int x = 400;
passengerslot = 0;
Passenger * list = new Passenger[x];
ifstream myfile("Airline.CSV");
if (myfile.is_open())
{
while (getline(myfile, line))
{
std::istringstream ss(line);
std::string token;
int dataslot = 0;
while (std::getline(ss, token, ',')) {
switch (dataslot){
case 0:
*last = token;
break;
case 1:
*first = token;
break;
case 2:
*rownum = atoi(token.c_str());
break;
case 3:
*seat = token.at(0);
break;
case 4:
*flightnum = atoi(token.c_str());
list[passengerslot] = *new Passenger(last, first, rownum, flightnum, seat);
cout << list[passengerslot].getPassenger() << endl; //prints as the passengers are assigned
if (passengerslot != 0){
cout << list[passengerslot - 1].getPassenger() << endl;
}
My passenger constructor:
Passenger::Passenger(string *fname, string *lname, int *rownum, int *flightnum, char *seatchar):firstname(fname), lastname(lname), rownumber(rownum), flightnumber(flightnum), seat(seatchar){
*firstname = *fname;
*lastname = *lname;
*rownumber = *rownum;
*flightnumber = *flightnum;
*seat = *seatchar;
}
The unusual dereferencing of pretty much everything has me worried that you may be reusing the same pointers over and over, writing over the same memory locations every time, then assigning those memory locations to pointers inside the Passenger.
For example, the constructor:
Passenger::Passenger(string *fname, string *lname, int *rownum, int *flightnum, char *seatchar):firstname(fname), lastname(lname), rownumber(rownum), flightnumber(flightnum), seat(seatchar)
{
*firstname = *fname;
*lastname = *lname;
*rownumber = *rownum;
*flightnumber = *flightnum;
*seat = *seatchar;
}
firstname(fname) means that what has to be a pointer to string, if this program compiles, firstname is being assigned the address pointed to by fname. The same pattern follows for the other members and associated parameter.
*firstname = *fname; does effectively nothing. It copies the string at fname over top of the string at firstname, which because of the assignment in the previous point are the exact same place.
This means that all of the Passenger::firstnames point to fname, all Passenger::lastnames point to the lname, etc... The end result is that all Passengers look exactly the same as the most recent Passenger.
Solution:
Stop using pointers for everything. You probably don't need any pointers.
The accumulation variables should be:
std::string first;
std::string last;
int rownum;
int flightnum;
char seat;
Passenger should be:
class Passenger
{
public:
Passenger(const std::string & fname,
const std::string & lname,
int rownum,
int flightnum,
char seatchar);
// other unspecified public methods
private:
std::string firstname;
std::string lastname;
int rownumber;
int flightnumber;
char seat;
// other unspecified private data and methods
}
and the constructor
Passenger::Passenger(const std::string & fname,
const std::string & lname,
int rownum,
int flightnum,
char seatchar):
firstname(fname), lastname(lname), rownumber(rownum),
flightnumber(flightnum), seat(seatchar)
{
}
Storage definition of
Passenger * list = new Passenger[x];
should be more along the lines of
std::vector<Passenger> list;
std::vector is a dynamically sizing array with all of the memory management required for most use cases built right in.
And rather than loading list with
list[passengerslot] = *new Passenger(last, first, rownum, flightnum, seat);
use
list.emplace_back(first, last, rownum, flightnum, seat);
Related
I have a
vector<string>data
organized as such
//NAME ID AGE
//NAME ID AGE
//NAME ID AGE
//NAME ID AGE
I can sort it by name alphabetically, how can I sort it in ascending order based on the 2nd column/3rd column instead? Thank you for any assistance and advice.
std::sort's third overload has a third parameter allows you to provide a function to do the ordering logic.
// get nth token from a string
std::string getnTh(const std::string & str, int n)
{
std::istringstream strm(str);
std::string result;
for (int count = 0; count < n; count++)
{
if (!(strm >> result))
{
throw std::out_of_range("ran out of tokens before n");
}
}
return result;
}
// get ID, second token, from string
std::string get_ID(const std::string str)
{
return getnTh(str, 2);
}
// compare the ID field, second token, in two strings
bool cmp_ID(const std::string &a, const std::string &b)
{
std::string tokena = get_ID(a);
std::string tokenb = get_ID(b);
return tokena < tokenb;
}
int main()
{
std::vector<std::string> data {"c c c ", "b b b " , "a a a"};
std::sort (data.begin(), data.end(), cmp_ID);
}
Note: This code could be crunched down a bit. I've broken it down step by step for easy reading.
Note: This is BRUTAL! It is constantly parsing the same strings over and over, a disgusting waste of effort.
Instead you should make a structure to store the already parsed string and store that structure in the std::vector.
// stores a person
struct person
{
std::string name;
std::string ID;
std::string age;
// constructor to parse an input string into a new person
person(const std::string & in)
{
std::istringstream strm(in);
if (!(strm >> name >> ID >> age))
{
throw std::runtime_error("invalid person input");
}
}
};
// much simpler and faster compare function. All of the parsing is done once ahead of time.
bool cmp_ID(const person &a, const person &b)
{
return a.ID < b.ID;
}
int main()
{
// replaces vector<string> data
std::vector<person> data {{"c c c"}, {"b b b"} , {"a a a"}};
std::sort (data.begin(), data.end(), cmp_ID);
}
You can read those Strings by each character until you hit the first/second space.
Then you should be able to "filter" out the first/second attribute.
In binary file, there are some information which is student id, name and GPA for three students. I want to read them. id is integer type and GPA is float type. name is string type and the size is not fixed because the size of name is different each other. I want to get id, name and GPA for each student at function of student class.
probably, I think I have a problem at loading name.
I want to know how to fix it.
#include <iostream>
#include <fstream>
#include <string>
using namespace std;
class Student {
private:
int id;
string name;
float GPA;
string line;
public:
Student() {};
Student(string line_) :line(line_) { };
int get_id();
string get_name();
float get_GPA();
void save_bi(ofstream &of);
void load_bi(ifstream &inf);
void save_txt(ofstream &of);
void print();
};
void Student::load_bi(ifstream &inf)
{
inf.read((char*)&id, sizeof(id));
inf.read((char*)&name, sizeof(name));
inf.read((char*)&GPA, sizeof(GPA));
//a.push_back(name);
}
int main()
{
ifstream inf;
inf.open("student_data.bin", ios::in | ios::binary);
if (!inf.is_open())
cout << "file open failure." << endl;
Student A;
A.load_bi(inf);
A.print();
Student B;
B.load_bi(inf);
B.print();
Student C;
C.load_bi(inf);
C.print();
inf.close();
return 0;
}
it is error message.
inline void _Container_base12::_Orphan_all() noexcept
{ // orphan all iterators
#if _ITERATOR_DEBUG_LEVEL == 2
if (_Myproxy != nullptr)
{ // proxy allocated, drain it
_Lockit _Lock(_LOCK_DEBUG);
for (_Iterator_base12 **_Pnext = &_Myproxy->_Myfirstiter;
*_Pnext != nullptr; *_Pnext = (*_Pnext)->_Mynextiter)
(*_Pnext)->_Myproxy = nullptr;
_Myproxy->_Myfirstiter = nullptr;
}
#endif /* _ITERATOR_DEBUG_LEVEL == 2 */
}
Exception thrown: read access violation.
_Pnext was 0x13AED64.
A simple example of serializing your structure:
class Student
{
int id;
string name;
float GPA;
string line;
public:
void binary_write(std::ostream& out) const;
void binary_read(std::istream& input);
};
void Student::binary_write(std::ostream& out) const
{
out.write(&id, sizeof(id));
std::string::size_type length(name.length());
out.write(&length, sizeof(length));
out.write(name.data(), length);
out.write(&GPA, sizeof(GPA));
length = line.length();
out.write(line.data(), length);
}
void Student::binary_read(std::istream& input)
{
input.read(&id, sizeof(id));
std::string::size_type length;
input.read(&length, sizeof(length));
char * temp = new char[length];
input.read(temp, length);
name = std::string(temp);
input.read(&GPA, sizeof(GPA));
delete[] temp;
input.read(&length, sizeof(length));
temp = new char[length];
input.read(temp, length);
line = std::string(temp);
delete [] temp;
}
The above code uses the technique of writing the length of the string, followed by the string contents. The reading consists of using a temporary buffer, then reads the text into the buffer and creates a std::string from the buffer.
There are other methods to read in a string (search your C++ reference for "c++ string input iterator), but this illustrates the point.
The problem with writing pointers, and std::string contains pointers, is that there is no guarantee that the operating system will load your program into the exact same place in memory at every invocation.
The binary_write function is declared as const since it doesn't change any of the Student data members. This technique is called "const correctness". Search the internet for its definition.
The default accessibility for class is private, so I removed the private from the section declaring the variables.
I am writing a function that reads various properties, including a string, from a csv file and assigns it to a relevant element of a struct which happens to be in an array of similar structs.
Whenever I attempt to assign value to:
materialLookup[v-1].name
the program crashes.
MaterialL
is a struct with a string element called name that looks like this:
struct MaterialL {
string name;
double sigma;
double corLength;
double mu;
double muPrime;
double ep;
double epPrime;
};
I have checked that I am reading the string from the csv file correctly and in this case, it is "Drywall". The program always crashes before I am able to cout<<"hey"; on the next line. My only thought is that because the program doesn't know the size of the string before I assign it, it doesn't leave any memory for it. If so how can I rectify this?
unsigned int getMatLookUp(string filename)
{
int nLines = getNumOfLines(filename);
cout << nLines;
materialLookup = (MaterialL*)alignedMalloc(nLines * sizeof(MaterialL));
ifstream file(filename);
int v = 0;
string value;
if (file.is_open() && fileExists(filename))
{
//flush title line
for (int p = 0; p < 6; p++){ std::getline(file, value, ','); }std::getline(file, value);
v++;
//get all the materials
while (v < nLines -1)
{
std::getline(file, value, ',');
cout << value<<"\n\n";
materialLookup[v - 1].name = value;
cout << "hey";
std::getline(file, value, ',');
cout << value << "\n\n";
materialLookup[v - 1].sigma = stod(value);
std::getline(file, value, ',');
materialLookup[v - 1].corLength = stod(value);
std::getline(file, value, ',');
materialLookup[v - 1].mu = stod(value);
std::getline(file, value, ',');
materialLookup[v - 1].muPrime = stod(value);
std::getline(file, value, ',');
materialLookup[v - 1].ep = stod(value);
std::getline(file, value);
materialLookup[v - 1].epPrime = stod(value);
v++;
}
file.close();
}
else
{
cout << "Unable to open file when loading material lookup in function getMatLookUp press enter to continue\n";
cin.get();
}
return(0);
}
The problem is most likely that your alignedMalloc uses malloc to allocate memory instead of new.
While both malloc and new allocates memory, the new operator does something that malloc does not: Call constructors.
If the name object is not constructed then it's basically in an invalid state and using it in any way will lead to undefined behavior.
The solution, if you keep on insisting on using malloc, or use new[] to allocate an array of bytes, is to use placement new to construct the name object in place:
materialLookup = (MaterialL*)alignedMalloc(nLines * sizeof(MaterialL));
for (size_t i = i; i < nLines; ++i)
{
new (&materialLookup[i].name) std::string;
}
I'm learning C++ and made myself a text file with over 10,000 lines. I'm trying to make a string array and insert the first line into the first array, the second line into the second array and so on. Here is what I've done so far:
ifstream theFile;
string inputFile;
cin >> inputFile;
theFile.open(inputFile.c_str());
const unsigned int ARRAY_CAP = 64U;
string line;
string *lineArr = new string[ARRAY_CAP];
if (theFile.is_open()) {
int lineNumber = 0;
while (!theFile.eof()) {
getline(theFile, line);
lineArr[i] = line;
i++;
}
}
A friend of mine told me to allocate the string array because I'm running out of memory, but I'm not even sure how to do that. How could I be able to allocate the string array?
If you want to stay with dynamically allocated arrays, you will need to expand them dynamically.
unsigned int lines_read = 0U;
std::string text_line;
unsigned int capacity = 4U;
std::string * p_array = new std::string[capacity];
while (std::getline(theFile, text_line))
{
p_array[lines_read] = text_line;
++lines_read;
if (lines_read > capacity)
{
// Allocate new array with greater capacity.
unsigned int old_capacity = capacity;
capacity = capacity * 2U;
std::string p_new_array = new std::string[capacity];
std::copy(p_array, p_array + old_capacity, p_new_array);
delete [] p_array;
p_array = p_new_array;
}
}
The std::vector performs similar memory management for you, so you don't have to do the above.
I have this structure:
struct student {
int id;
string name;
string surname;
};
What I need to do is to make function with this declaration:
char* surname_name (student Student)
which will format every Student that I put in format like this "surname, name" and it will bring back pointer on it.
What I've done so far is this:
char* surname_name (student Student){
char *pointer= (char*) malloc (sizeof(char)*(Student.name.length + Student.surname.length + 2)); // + 2 because of space and comma
string::iterator it;
int i=0;
for (it= Student.surname.begin(); it != Student.surname.end(); it++){
(*pointer)[i] = it; // here it gives me error
}
... // here still should be added code for comma, space and name
return pointer;
}
I can't make it anyhow else, because it's in the task that function needs to have this declaration. How to make this properly?
This should do the trick:
char * surname_name (student Student){
return strdup((Student.surname + ", " + Student.name).c_str());
}
(*pointer)[i] = it;
should be
*(pointer+i) = *it; //assigning the current char to correct position
you should also increase i properly.
You can also do it with std::string which can do simple concatenation.
I prefer to use std::string::c_str:
string surname_name (const student &Student)
{
return Student.name + " " + Student.surname;
}
// ...
do_something( surname_name(student).c_str() );
If you really want to return a pointer, you can do it as below:
char *surname_name (const student &Student)
{
string s = Student.name + " " + Student.surname;
char *p = new char [s.length()+1];
strcpy(p, s.c_str());
return p;
}
Don't forget to delete the returned pointer.