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.
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 need to create a shared_ptr inside function and store it inside member class vector, but also I need to return it.
for example
create Person Class
class Person {
private:
std::string name;
public:
Person(std::string name) : name(std::move(name)) {}
const std::string &getName() const {
return name;
}
void setName(const std::string &name) {
Person::name = name;
}
};
then have another class that contains a vector of shared_ptr
class Agenda{
private:
std::vector<std::shared_ptr<Person>> people;
public:
std::shared_ptr<Person> addPerson(const std::string& name){
auto p = std::make_shared<Person>(name);
people.push_back(p);
return p;
}
};
so in my addPerson function I need to create the Person and add it to my vector, but also I need to return it. what is the best way do to this?
1.
create the local shared_ptr push in the vector and return, I suppose this is safe because I'm returning the shared_ptr as value but whats happen here, I'm creating two shared_ptr? one that lives inside the vector and the other that I'm returning?, also if the user of this functions reset the returned shared_ptr whats happen? will affect the shared_ptr inside the vector?
std::shared_ptr<Person> addPerson(const std::string& name){
auto p = std::make_shared<Person>(name);
people.push_back(p);
return p;
}
2.
use std::make_shared inside the push_back then find the element inserted and then return const reference to the Person not the shared_ptr, whats happen here?
const Person& addPerson(const std::string& name){
people.push_back(std::make_shared<Person>(name));
return *people.back();
}
maybe is best return a std::weak_ptr?
I want to know what is the best practice cons and pro to do this.
Uncle Bob in his Clean Code suggests that no more than 3 arguments should a function get:
Functions that take three arguments are significantly harder to
understand than dyads. The issues of ordering, pausing, and ignoring
are more than doubled. I suggest you think very carefully before
creating a triad.
But what about CTOR arguments in class inheritance hierarchy? What if each class in hierarchy adds a new field and you should initialize them in CTOR. See an example below:
class Person
{
private:
std::string m_name;
int m_age;
public:
Person(const std::string& name, const int age);
std::string getName() const { return m_name; }
int getAge() const { return m_age; }
~Person();
};
class Student : public Person
{
private:
std::string m_university;
int m_grade;
public:
Student(const std::string& name, const int age, const std::string& university, const int grade);
std::string getUniversity() const { return m_university; }
int getGrade() const { return m_grade; }
~Student();
};
See how Student gets 4 arguments, while Person gets only 2 and Student adds two more. So how we should handle this?
There are several ways.
Combine multiple parameters into a struct
struct PersonInfo {
std::string name;
int age;
};
struct StudentInfo {
PersonInfo person_info;
std::string university;
int grade;
};
Person::Person(const PersonInfo &info) :m_name(info.name), m_age(info.age) {}
Student::Student(const StudentInfo &info) : Person(info.person_info), m_university(info.university), m_grade(info.grade) {}
Default initialize data members and set them with setter utilities
Person::Person() : m_age(0) {}
void Person::set_age(int age) { m_age = age; }
Student() : m_grade(0) {} // Person is default constructed.
void Student::set_grade(int grade) { m_grade = grade; }
i'd say this was just a suggestion. it's fully up to you - how many arguments should your functions get.
but if you prefer to follow the rule, make some sort of parameters holder, like:
class Student
{
public:
struct StudentParameters
{
...
};
Student(name, age, const StudentParameters &sp);
...
};
You're confusing two distinct meanings of the word function.
The first meaning is more related to the original mathematical meaning of the word. In this case, function is a named relation between one or more inputs and exactly one output. The "Clean Code" rules refers to this meaning, and tells you that more should be limited to 3 inputs.
The alternative meaning in C++ refers to a block of code, which may or may have inputs, which may or may have an output, which may or may have a name.
And yes, even in the latter sense, constructors are unusual functions. They never have a return type, not even void, and they don't have names. So you can rationalize that they're also special when it comes to their number of input arguments.
First of all, I have only learned a little bit of Java before. It's been only a few days since I started getting friendly with C++ so please don't take this question so basic and please don't degrade my question.
I made a simple source code as follows:
#include <iostream>
using namespace std;
class Car {
public:
void setBrand(string name);
void setPrice(double price);
string getBrand();
double getPrice();
Car();
Car(string name);
Car(string name, double price);
private:
string name;
double price;
};
Car::Car() {
}
Car::Car(string name) {
name = name;
}
Car::Car(string name, double price) {
name = name;
price = price;
}
void Car::setBrand(string name) {
name = name;
}
void Car::setPrice(double price) {
price = price;
}
string Car::getBrand(void) {
return name;
}
double Car::getPrice(void) {
return price;
}
int main() {
Car car;
car.setBrand("Nissan");
car.setPrice(30000);
cout << "Brand: " << car.getBrand() << endl;
cout << "Price: " << car.getPrice() << endl;
return 0;
}
I wanted to make a code that creates an empty instance of a class called Car, set the field values later and print them out on the console.
The code did not make any errors during the compile, but the result I see was totally different from what I expected. It didn't show the brand name and the price was looking even weird, as follows.
Brand:
Price: 6.95322e-310
Somebody help me out! Thank you very much indeed in advance.
The problem you have is that you override the member names with function parameters. You can use this-> to make it explicit or name the member differently.
For example:
void Car::setBrand(string name) {
this->name = name;
}
Or:
void Car::setBrand(string new_name) {
name = new_name;
}
In your constructor and setters, you make no differentiation between the local parameter and the class member.
name = name;
Both the function parameter and the class member are called name. Currently the compiler is assigning the parameter value to itself, and not affecting the class member at all. This is because the function parameter is in a more immediate scope.
Possible solutions:
Specify this when referring to the class member: this->name = name;.
Rename the function parameter: name = _name;.
For the constructor, use initializer lists:
Car::Car(string name, double price)
: name(name)
, price(price)
{ }
There's too much wrong with your code to describe it in prose, so let me present a fixed implementation, and I leave it to you to spot the difference:
#include <string>
class Car
{
private:
static constexpr double kNoPrice = -1.0;
static constexpr const char* kNoName = "[no name]";
public:
// Main constructor: constructs a car with the given name and price.
Car(std::string name, double price)
: name_(std::move(name))
, price_(price)
{}
// Convenience constructors:
Car() : Car(kNoName, kNoPrice) {}
Car(std::string name) : Car(std::move(name), kNoPrice) {}
// Accessors:
const std::string& getBrand() const { return name_; }
void setBrand(std::string name) { name_ = std::move(name); }
double getPrice() const { return price_; }
void setPrice(double price) { price_ = price; }
private:
std::string name;
double price;
};
Some random notes, in no particular order:
Use correct names. It's std::string, not string, mate or buddy. Never ever be abusing namespace std.
Include headers for external names that you need.
Reading uninitialized values is undefined behaviour, so none of your constructors should leave fields uninitialized (like price_).
Give private members consistent names (e.g. foo_ in my example).
Accessors should be const-correct.
Convenience constructors should delegate to one single work-horse constructor.
Pick sensible defaults for initial values of defaulted fields and make them discoverable.
Use move semantics when taking ownership of dynamically managed data (strings, dynamic containers, etc.).
vector<string> hj{ "jack" };
vector<double> x{ 8 };
NamePairs pair1(hj,x);
This is the only way the code runs. Is there a way to pass the values directly to pair1 object instance
You are passing the wrong types to your constructor:
"Jack" is of type const char[5] and the second argument 28.2 is of type double
Your constructor though is expecting a std::vector<string> and a std::vector<double>. So the problem is your constructor is expecting a "list" of strings and doubles, what you are not giving him.
Considering the name of your class the correct solution should be:
class NamePairs
{
private:
double age;
std::string name;
public:
NamePairs(const std::string& name, double Age)
{
this->name = name;
this->age = age;
}
};
Now you can instantiate it like that:
NamePairs pair("Alfred", 43.4);
Also consider using a std::pair
Considering you want a list of Persons that each have a Name and Age there a some different Solutions:
You could create a class Person that has two attributes:
class Person
{
private:
double Age;
std::string Name;
public:
Person(double age, const std::string& name)
{
Age = age;
Name = name;
}
};
And use it like that:
std::vector<Person> persons;
persons.push_back(Person(23.4, "Alfons");
Or you also could (more like your try) create a class PersonList like this:
class PersonList
{
private:
std::vector<double> Ages;
std::vector<std::string> names;
public:
void addPerson(double Age, const std::string& Name)
{
Ages.push_back(Age);
Names.push_back(Names);
}
};
And use it like that:
PersonList list;
list.addPerson(23.5, "Andrea");
I would greatly prefer the first Approach because it is way easier to handle the Persons if they're are not in the list(e.g. returned by a accessor or passed around to other functions/objects or if you Need to operate upon them). And it Looks way cleaner to me