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.
Related
Im new to C++ and I am curious to know if you can create multiple constructors with the same arguments. Say for example I have this class in which I have patients and I have their name and their age. I know I can create a constructor like this:
class hospital {
hospital(){
setname("John");
setage(24);
}
private:
string name;
int age;
};
but could I create another constructor just like I did above. Something like:
hospital patientBilly(){
setname("Billy");
setage(32);
}
The problem is that you redefine the constructor. Allowed is only one definition.
Simplified example:
void myFunc (){};
void myFunc (){};
int
main ()
{
myFunc ();
}
I whould make the Hospital class like this:
#include <string>
struct Hospital // struct here to signal we have no invariant. you could also use a class and make the member public
{
std::string name{}; // no setter and getter if you do not have invariant.
int age{};
};
int
main ()
{
auto hospital = Hospital{ .name = "John", .age = 42 }; //c++20 Designated Initializers so we can construct Hospital with a name and age without defining a constructor
}
I believe you are currently only a bit confused. So lets become the things sorted...
A class describes how objects should behave. The constructor is part of that description and equal to all the instances it will later on create. Your first step for understanding should be: There is a single class and multiple instances/objects of it.
So you write a single class and give for each of the instances/objects different parameters to get different objects.
Example:
class hospital {
public:
hospital(const std::string& name_, int age_ ):
name { name_ }, age{ age_ }{
}
void Print() const
{
std::cout << "Hospital" << name << ":" << age << std::endl;
}
private:
std::string name;
int age;
};
int main()
{
hospital hospital1{ "John", 24 };
hospital hospital2{ "Bill", 77 };
hospital1.Print();
hospital2.Print();
}
You can also create a different class for every of your later created objects, but I believe that is never what you want to do, especially not at the beginning of your C++ career!
If you want to create some kind of list of instances, you can store the objects in containers and act on the containers as you like.
int main()
{
std::vector< hospital > objs;
objs.emplace_back( "John", 24 );
objs.emplace_back( "Bill", 77 );
for ( const auto& hos: objs )
{
hos.Print();
}
}
In your problem you have two concepts, which you are trying to mix.
hospitals and patients. So it makes sense to model them as two distinct classes.
This way you can model a patient as something that has an age and a name.
And a hospital as something that "contains" patients.
Give the patient a contructor where you can pass age and name.
And give the hospital a method or methods to add patients.
In the example I show to variants of how you could add patients to a hospital.
I also have use unsigned variable types for numbers that can never be smaller then 0. And I use the const keyword a lot for places in the code where values must only be used, and should not be changed.
#include <iostream>
#include <string>
#include <utility>
#include <vector>
//---------------------------------------------------------------------------------------------------------------------
class patient_t
{
public:
// You need a constructor like this
patient_t(const unsigned int age, const std::string& name ) :
m_age{ age },
m_name{ name }
{
}
// getter function for observing private data
const unsigned int& age() const noexcept
{
return m_age;
}
// getter function for observing private data
const std::string& name() const noexcept
{
return m_name;
}
private:
unsigned int m_age;
std::string m_name;
};
// a useful output function to have (will make code later shorter)
std::ostream& operator<<(std::ostream& os, const patient_t& patient)
{
os << "Patient : " << patient.name() << ", age : " << patient.age() << std::endl;
return os;
}
//---------------------------------------------------------------------------------------------------------------------
class hospital_t
{
public:
void add_patient(const unsigned int age, const std::string& name)
{
m_patients.emplace_back(age,name); // will call patient constructor with two parameters age and name and puts it into vector
}
void add_patient(const patient_t& patient)
{
m_patients.push_back(patient); // store a copy of patient in the vector
}
const auto& patients() const
{
return m_patients;
}
private:
std::vector<patient_t> m_patients;
};
//---------------------------------------------------------------------------------------------------------------------
int main()
{
hospital_t hospital;
patient_t billy{ 42, "Billy" };
hospital.add_patient(billy);
hospital.add_patient(31, "Jane");
for (const auto& patient : hospital.patients())
{
std::cout << patient;
}
}
If I have understood correctly then what you need is to define two constructors
hospital( const std::string &name, int age )
{
setname( name );
setage( age );
}
hospital() : hospital( "John", 24 )
{
}
Then you will can write declaring an object of the class
hospital patientBilly( "Billy", 32 );
class myItem {
int key;
string name;
public:
// Constructor
myItem(int key, string name)
{
key = key;
name = name;
}
};
class myCollection {
vector<myItem> col;
public:
void insert(myItem &i);
void print();
};
int main() {
myCollection c;
int key;
string name;
cin >> key;
cin >> name;
myItem i = myItem(key, name);
c.insert(i);
c.print();
}
When I try to compile this, I get the error: no matching function for call to ‘myItem::myItem()’ and note: candidate: myItem::myItem(int, std::string). candidate expects 2 arguments, 0 provided. How might I fix this? Thanks!
First off, I am not a fan of confusing the compiler with similar names. This is terrible practice (and I am not a fan of the java this.local = param)
And even if you can argue the compiler knows what is right, someone maintaining your code will not. So you could at least capitalize or suffic and underscore or something else if you don't want to rename (sometimes I saw prefixing it with in, like inKey, inName as an alternative method which I am all in favor for, the underscore is a good if you don't want to make the name change to stand out, just make sure the underscore is not in front of the variable/parameter name):
int key;
string name;
public:
// Constructor
myItem(int key_, string name_) : key(key_), name(name_) {}
Moving on, I don't get your error but I do get unresolved externals. The problem is myCollection is defined, but not implemented (or rather its methods aren't). Try an empty placeholder using {}.
class myCollection {
vector<myItem> col;
public:
void insert(myItem &i) {}
void print() {}
};
This should fix your errors.
Your Constructor is named Team and not myItem
Replace it with
myItem(int key, string name)
{
key = key;
name = name;
}
The vector you are using in myCollection internally uses the default constructor. Try using a pointer of myItem in the vector or specify the default and copy constructors too.
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
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.