I have a quite simple situation I don't know how to resolve :
I am parsing a file with addresses (city, street and street number), and have the following class :
class Address
{
std::string city;
std::string street;
std::string number;
};
I would like to create an object for each address I find in the file, but I can't know how many there are since the file can change. Is there a way to create an Array of objects ; or any more suitable solution ?
NOTE : Parser works fine, all there is to do is to set the values in the objects.
You can use std::vector for such purpose: http://en.cppreference.com/w/cpp/container/vector
#include <vector>
struct Address
{
std::string city;
std::string street;
std::string number;
};
bool parseAddress(Address& address)
{
//TODO: implement
//TODO: return "true" if another address has been successfully parsed
}
int main()
{
std::vector<Address> addresses;
Address current;
while(parseAddress(current))
{
addresses.push_back(current);
}
return 0;
}
Like #Serge but rather than use the parser directly define an input operator.
struct Address
{
std::string city;
std::string street;
std::string number;
friend std::istream& operator>>(std::istream& in, Address& address) {
return parseAddress(in, address);
}
};
std::istream& parseAddress(std::istream& in, Address& address)
{
//TODO: implement
//TODO: return stream.
// If the parse failed.
// Then set the bad bit on the stream.
}
int main()
{
std::ifstream file("address.txt");
std::vector<Address> addresses(std::istream_iterator<Address>(file),
std::istream_iterator<Address>());
}
Related
New to classes and objects in c++ and trying to learn a few basics
I have the class TStudent in which the Name, Surname and Age of student are stored, also I have the constructor which is accessed in main and inserts in the data.
What I want to do is: having the class TRegistru, I have to add my objects data in it, in a way that I can store it there, then I could save the data in data.bin and free the memory from the data, then I want to put the data back in the class and print it out.
The question is: In what way & what is the best way to add my objects in the second class, so that I could eventually work with them in the way I've described in the comments, so that I won't have to change nothing in main
Here's my code so far:
#include <iostream>
using namespace std;
class TStudent
{
public:
string Name, Surname;
int Age;
TStudent(string name, string surname, int age)
{
Name = name;
Surname = surname;
Age = age;
cout <<"\n";
}
};
class TRegistru : public TStudent
{
public:
Tregistru()
};
int main()
{
TStudent student1("Simion", "Neculae", 21);
TStudent student2("Elena", "Oprea", 21);
TRegistru registru(student1);//initialising the object
registru.add(student2);//adding another one to `registru`
registru.saving("data.bin")//saving the data in a file
registru.deletion();//freeing the TRegistru memory
registru.insertion("data.bin");//inserting the data back it
registru.introduction();//printing it
return 0;
}
Hence the question is about passing data from A to B, I will not comment on the file handling portion.
This can be done in multiple ways, but here is one of the simplest and most generic. By calling TRegistru::toString() you serialize every TStudent added to TRegistru into a single string which then can be easily written to a file.
Demo
class TStudent
{
public:
std::string Name, Surname;
int Age;
std::string toString() const
{
return Name + ";" + Surname + ";" + to_string(Age);
}
};
class TRegistru
{
public:
void add(const TStudent& student)
{
students.push_back(student);
}
void deletion()
{
students.clear();
}
std::string toString() const
{
std::string ret{};
for(const auto& student : students)
{
ret += student.toString() + "\n";
}
return ret;
}
std::vector<TStudent> students;
};
I have a function read() that reads a file and store information into a map. However, whenever the function calls map.insert(), it gives me an error.
Employee and Volunteer are two customs classes with only a few variables.
For example, If I call
ifstream fin;
std::map<std::string, Employee*> employees;
fin.open("Employee.txt");
read<Employee, EMPLOYEE_SIZE>(fin, employees);
It gives the following error:
std::_Tree<std::_Tmap_traits<_Kty,_Ty,_Pr,_Alloc,false>>::insert(std::initializer_list<std::pair<const std::string,Employee *>>)': cannot convert argument 1 from 'initializer list' to 'std::initializer_list<std::pair<const std::string,Employee *>>'`.
Here is the function(Both Employee and Volunteer has the same base class:)
template <typename T, int T_LINE_SIZE>
inline void read(std::ifstream& in, std::map<std::string, T*>& input) {
std::string name = "";
std::string ID="";
std::string line;
double salary;
while (getline(in,line)) {
if (typeid(T) == typeid(Volunteer)) {
name = line.substr(0, 19);
ID = line.substr(20, T_LINE_SIZE);
input.insert({name,new Volunteer(ID,name)}); //error happens here
}else if (typeid(T) == typeid(Employee)) {
name = line.substr(0, 19);
ID = line.substr(20, 29);
salary = std::stod(line.substr(30, T_LINE_SIZE));
input.insert({ name,new Employee(ID, name, salary) }); //error happens here
}
}
}
What am I doing wrong here?
Try following code:
template <typename T, int T_LINE_SIZE>
inline void read(std::ifstream& in, std::map<std::string, T*>& input) {
std::string name = "";
std::string ID="";
std::string line;
double salary;
while (getline(in,line)) {
if constexpr (std::is_same<T, Volunteer>::value) {
name = line.substr(0, 19);
ID = line.substr(20, T_LINE_SIZE);
input.insert({name,new Volunteer(ID,name)});
}else if constexpr (std::is_same<T, Employee>::value) {
name = line.substr(0, 19);
ID = line.substr(20, 29);
salary = std::stod(line.substr(30, T_LINE_SIZE));
input.insert({ name,new Employee(ID, name, salary) });
}
}
}
The reason is that you are using templates. Based on the type of T when you call read() function, type of T is incorrect in scope of one if condition for calling insert() method. This happens because all possible outcomes of code need to be checked at compile time for different types. But when you use if constexpr, that part of code is ignored at compile time, so incorrect part of code will not be seen during compiling code and your code compiles correctly.
#Afshin has shown one way you could do this, but I'd suggest that it's probably not the best way to do the job, at least as a rule.
By distributing the logic a bit differently, you can end up with code that I consider quite a bit simpler.
I would start by overloading operator>> to read a an object of each class you need to store:
struct Volunteer {
std::string name;
std::string ID;
friend std::istream &operator>>(std::istream &is, Volunteer &v) {
std::string line;
std::getline(is, line);
v.name = line.substr(0, 19);
v.ID = line.substr(20); // captures the rest of the line
return is;
}
};
struct Employee {
std::string name;
std::string ID;
double salary;
friend std::istream &operator>>(std::istream &is, Employee &e) {
std::string line;
std::getline(is, line);
e.name = line.substr(0, 19);
e.ID = line.substr(20, 29);
e.salary = std::stod(line.substr(30);
return is;
}
};
I may have omitted other (possibly important) things from Volunteer and Employee--I've only included enough to read one from a file, and store the data we read.
With those in place, reading them from a file becomes quite a bit simpler:
template <class T>
inline void read(std::istream &is, std::map<std::string, T *> &input) {
T t;
while (is >> t)
input.emplace(t.name, new T(t));
}
In this design, Employees and Volunteers each know how to read their own data from a file, and the code that reads the records doesn't need to know the details of how each is read. This makes the code rather more extensible. For example, let's consider what happens when we add a further class for Intern. In your original code (as repaired by Afshin), you'd add a third leg to the if/else if chain:
else if constexpr (std::is_same<T, Intern>::value) {
// code to read and insert data for an Intern here
}
By contrast, with the design I'd advocate, all the code to deal with Interns would be in (or at least associated with) the Intern class itself:
struct Intern {
std::string name;
std::string ID;
bool paid;
double salary;
friend std::istream &operator>>(std::istream &is, Intern &i) {
// code to read data for an Intern here
}
};
...So in this case, the code to read data from a file doesn't require any changes to work with a file of records for Interns instead of Employees or Volunteers.
I am currently making a project where in a class I will be taking in a large amount of variables from a user input. Is there a way to stop something like this:
class Person
{
std::string firstName,
lastName,
DoB,
address;
int personID,
durationMins,
totalVisits;
void setValues(std::string values[])
{
firstName = values[0];
lastname = values[1];
DoB = values[2];
// ... etc
}
};
I would like to avoid having a mass of lines dedicated to variable assignment, though I do not know if this is even possible. Any help would be appreciated, thank you.
That can be solved with a simple variadic template function:
template <typename T, typename ...P> void AssignArrayElementsTo(const T *array, P &... objects)
{
std::size_t i = 0;
((objects = array[i++]) , ...);
}
// ...
void setValues(std::string values[])
{
AssignArrayElementsTo(values, firstName, lastName, DoB, address);
}
Unfortunately C++ doesn't have reflection (yet), so you still need to manually list all needed class members. There is no way around that.
In C++11 and over, you can use std::reference_wrapper or the helper function std::ref to generate the reference array of member strings and then the range-based-for of this array is applicable to the initialization as follows:
DEMO
void setValues(std::string values[])
{
std::size_t i = 0;
for(auto& str : {std::ref(firstName), std::ref(lastName), std::ref(DoB), std::ref(address)}){
str.get() = std::move(values[i++]);
}
}
Addition
As #HolyBlackCat suggested in comments, this looks more simple:
DEMO
void setValues(std::string values[])
{
std::size_t i = 0;
for(auto* str : {&firstName, &lastName, &DoB, &address}){
*str = std::move(values[i++]);
}
}
Since you are taking input from the user presumably via some kind of std::istream, an elegant way to take input would be to overload the insertion operator.
class Person {
/* ... */
friend std::istream& operator>>(std::istream& stream, Person& p) {
stream >> p.firstName >> p.lastName >> p.DOB;
/* might not be very straightforward to input address as you need to take care of spaces */
/* put address processing logic here */
std::getline(stream, p.address);
return stream;
}
};
int main () {
Person person;
std::cin >> person;
return 0;
}
You can also overload the extraction operator.
I have a list of objects derived from a class named "Campus" which contains two strings, one int and two lists : one for "Students", the other is for "Teachers", before closing the program, I want to save the campus objects and of course the "Student" and "Teachers" objects contained on their lists, I want to serialize those data in an XML or JSON format or even anything else then store the result in a file.
Can somebody give me the fastest way to do the serialization with a library (that is not heavy as boost) in XML or JSON or another solution. When it comes to deal with JSON or XML serialization, I don't know what to do !
EDIT: is this feasible with RapidJSON ?
class Campus
{
private:
std::string city;
std::string region;
int capacity;
std::list<Student> students;
std::list<Teacher> teachers;
}
class Student
{
private:
int ID;
std::string name;
std::string surname;
}
class Teacher
{
protected:
int ID;
std::string name;
std::string surname;
};
You can use this C++ serialization library : Pakal persist
#include "XmlWriter.h"
class Campus
{
private:
std::string city;
std::string region;
int capacity;
std::list<Student> students;
std::list<Teacher> teachers;
public:
void persist(Archive* archive)
{
archive->value("city",city);
archive->value("region",region);
archive->value("capacity",capacity);
archive->value("Students","Student",students);
archive->value("Teachers","Teacher",teachers);
}
}
class Student
{
private:
int ID;
std::string name;
std::string surname;
public:
void persist(Archive* archive)
{
archive->value("ID",ID);
archive->value("surname",surname);
archive->value("name",name);
}
}
class Teacher
{
protected:
int ID;
std::string name;
std::string surname;
public:
void persist(Archive* archive)
{
archive->value("ID",ID);
archive->value("surname",surname);
archive->value("name",name);
}
};
Campus c;
XmlWriter writer;
writer.write("campus.xml","Campus",c);
Unfortunately C++ doesn't support reflection, so it can't automagically figure out the parameter names.. but check out this answer which looks like it'll be close to what you want: https://stackoverflow.com/a/19974486/1715829
First of all I wanna say that I am very new to CPP (I started with cpp11) :)
Considering the following entities: Student(first name + last name) and Group (description + more students).
I created the following 2 classes in C++:
class Student
{
private:
std::string firstName;
std::string lastName;
Student(const Student &student);
Student& operator=(const Student &student);
public:
Student():firstName(""), lastName("") { }
Student(std::string firstName, std::string lastName):firstName(firstName), lastName(lastName) { }
Student(const Student &&student):firstName(student.firstName), lastName(student.lastName) { }
Student& operator=(const Student &&student) { this->firstName=student.firstName; this->lastName=student.lastName; return *this; }
std::string GetFirstName() const { return this->firstName; }
std::string GetLastName() const { return this->lastName; }
};
class Group
{
private:
std::string description;
std::vector<std::shared_ptr<Student>> students;
Group(const Group &group);
Group& operator=(const Group &group);
public:
explicit Group():description(""), students(std::vector<std::shared_ptr<Student>>()) { }
explicit Group(std::string description) :description(description), students(std::vector<std::shared_ptr<Student>>()) { }
void NewStudent(Student &&student) { students.push_back(std::make_shared<Student>(std::move(student))); }
std::vector<std::shared_ptr<Student>> GetStudents() const { return students; }
};
In main I have this:
Student s1("fn1","ln1");
Student s2("fn2","ln2");
//Student s3("fn3","ln3");
Group cppGroup("C plus plus");
cppGroup.NewStudent(std::move(s1));
cppGroup.NewStudent(std::move(s2));
cppGroup.NewStudent(Student("fn3", "ln3"));
//cppGroup.NewStudent(s3);
std::vector<std::shared_ptr<Student>> cppStudents=cppGroup.GetStudents();
My question is related to NewStudent method.
In the first 2 cases the parameter is move(s) and in the third case is Student(...).
My guess is that Student("fn3", "ln3") is the same as Student s3("fn3, "ln3") but if i pass s3 to the function it just won't compile with the following error: cannot convert from Student to Student&&
PS: I would appreciate if you helped me understand how to make the example I considered ideal.
Thank you very much.
LE: I think I understand what is happening, Visual Studio shows the following error: cannot convert an lvalue to a rvalue so my guess is that if I pass to NewStudent s3 it doesn't know how to convert it to a rvalue but if i pass it Student("fn3", "ln3") if will call the move constructor.
If that is really your design, you can simplify it a lot and do away with all the smart pointers and custom structors:
class Student
{
private:
std::string firstName;
std::string lastName;
public:
Student(std::string firstName, std::string lastName):firstName(firstName), lastName(lastName) { }
std::string GetFirstName() const { return this->firstName; }
std::string GetLastName() const { return this->lastName; }
};
class Group
{
private:
std::string description;
std::vector<Student> students;
public:
explicit Group(std::string description) :description(description) { }
void NewStudent(Student student) { students.push_back(student); }
std::vector<Student> GetStudents() const { return students; }
};
Student("fn3", "ln3") is a temporary object which does not posses a name. The compiler decides it can give it away because you have no chance of using it again. In the case of Student s2("fn2","ln2")
you are keeping a reference to the object in the variable s2. So you must explicitly give it away with the move statement.
I suggest changing your design and using smart pointers.
Have one container for all of the students.
A Group of students would have one or more smart pointers to students in the "all students" container.
This design allows you to have different themed groups without copying the student objects. The Group contain would contain copies of the smart pointers.
Using this design, you can also create indexes to order your students by differing criteria. For example, you could have an index of students sorted by first name and another index of students sorted by last name.