2

Apologies in advance if this is below the community's paygrade. I just started learning about OOP and English is not my native language.

I just learned about using getters and setters, and the importance of data encapsulation. However, All the examples I've seen online using getters and setters all deal with static values such as the one below:

#include <iostream>
using namespace std;

class Employee {
  private:
    // Private attribute
    int salary;

  public:
    // Setter
    void setSalary(int s) {
      salary = s;
    }
    // Getter
    int getSalary() {
      return salary;
    }
};

int main() {
  Employee myObj;
  myObj.setSalary(50000);
  cout << myObj.getSalary();
  return 0;
}

I've managed to allow user input by declaring another set of variables and feeding them as parameters into the setters like below:

int main() {
  Employee myObj;
  int salary;
  cout<<"Enter salary: " << endl;
  cin>>salary;
  myObj.setSalary(salary);
  cout << myObj.getSalary();
  return 0;
}

Is this the only way to allow user input? Is there a more elegant way of accomplishing this other than declaring another set of local variables?

Sorry if this is a beginner question. I'm new to the C++ game and am still getting my feet wet.

  • 2
    yep, perfectly reasonable, just needs some error checking to make sure `cin>>salary` was successful – Alan Birtles Oct 15 '20 at 10:57
  • @AlanBirtles thank you. I can't shake the feeling that I'm doing something wrong by declaring another set of local variables specifically for the purpose of passing into private class members – 儚き 戦士の翼 Oct 15 '20 at 11:03
  • you could wrap it in a function to be able to write `myObj.setSalary( get_salary_from_user_input() );` but that wouldn't change much, only make your code more / less clear (depedning on your personal preference) – 463035818_is_not_an_ai Oct 15 '20 at 11:21
  • @idclev463035818 I was hoping I could do some variation of ```myObj.setSalary(cin)``` so ```cin``` would feed the value directly into the getter without me having to pass it into temp variables – 儚き 戦士の翼 Oct 15 '20 at 11:25

3 Answers3

1

Is this the only way to allow user input? Is there a more elegant way of accomplishing this other than declaring another set of local variables?

(and from comment)

I was hoping I could do some variation of myObj.setSalary(cin) so cin would feed the value directly into the getter without me having to pass it into temp variables

I would not mix input from stdin with setters, better keep them as seperate methods:

class foo {
    int x;
    int other_member;
public:
    void set_x(int a) { x = a; }
    void set_other_member(int a) { other_member = a; }
    void read_x(std::istream& in) {
         in >> x;
    }
};

This lets you write

f.read_x(std::cin);

to read x from stdin.

Note that there are many ways to accomplish the same and what is "elegant" is rather subjective. Typically you would provide an overload for operator>> to read a foo from a stream:

std::istream& operator>>(std::istream& in, foo& f) {
    int a,b;
    in >> a >> b;
    f.set_x(a);
    f.set_other_member(b);
}

Either this (using the setters) or you make the operator a friend of foo (to access privates directly) or you use foo::read_x to implement it. Then you can write

foo f;
std::cin >> f;
463035818_is_not_an_ai
  • 109,796
  • 11
  • 89
  • 185
  • Thank you. This looks much better. I think I'll write a separate method for user input then. That second example with streams and operators flew right over my head, but I'll add that to my read-up list. – 儚き 戦士の翼 Oct 15 '20 at 11:52
  • @儚き戦士の翼 i strongly suggest you to take a look at overloading `operator>>` (and `<<`), it is extremely useful. See here for some details https://stackoverflow.com/questions/4421706/what-are-the-basic-rules-and-idioms-for-operator-overloading/16615725#16615725 (look for "input output operators") – 463035818_is_not_an_ai Oct 15 '20 at 11:55
1

Note that C++ is not a "pure" OOP language, and idiomatic C++ will happily eschew certain OOP principles in favor of simpler and more effective code where applicable.

It is perfectly acceptable, if you want a datatype to just "hold" data, to use a plain old struct (with public data fields). Don't confuse the benefits of encapsulation (...of non-trivial constructs, so you can change their implementation internally) with any kind of "security". Getters/Setters or not, anyone can change your object's data if he wants to. Incidentially, this makes reading data easier.

cin >> e.salary;

If you don't want "just a struct", it is good style to have a constructor that sets meaningful initial values, so that you don't have "uninitialized" Employee objects flying around (RAII). This means that neither your client nor your destructor has to deal with the possibility that Employee might not actually be one (hold null pointers, or other kinds of uninitialized resources).

Employee e( "Mark Smith", 10000 );

Once you are at that point, Employee should be responsible for anything that happens to / with an Employee object. That implies that it should not be somebody else that's reading user input and writes it to Employee data fields (via setters or not), it should be a message sent to the Employee object that it should update its fields, from input or otherwise, as appropriate.

(Consider the possibility of someone setting a pointer member of yours to nullptr, or worse, some random value. Your destructor would throw a fit trying to free the resource, and then how you'd debug where that invalid pointer came from?)

At which point we arrive at the operator>>(), which can be overloaded for istreams and your class.

class Employee
{
    public:
        // ...
        friend istream & operator>>( istream &, Employee & );
    // ...
};

istream & operator>>( istream & in, Employee & e )
{
    getline( e.name, in );
    in >> e.salary;
    return in;
}

int main()
{
    Employee e; // For the sake of example, a default constructed employee
    cout << "Enter new employee's name (on its own line), and salary:\n";
    cin >> e;
}

And once there, you get an idea of what data encapsulation actually means: Employee is an object, not a data container. You should not poke around in it's innards, you should send messages.

Employee e( "Mark Smith", Role::INTERNEE, 10000 );
cout << e; // Output: "Mark Smith, Internee, with an income of 10.000 sicles."
e.promote( Role::TEAM_LEAD, 10000 ); // Exception: "Wrong pay scale for Team Leads."
e.promote( Role::TEAM_LEAD, 30000 );
cout << e; // Output: "Mark Smith, Team Lead, with an income of 30.000 sicles and a free car."

Note how the Employee class sanity-checked the new salary, and knew that a promotion to Team Lead automatically included a free car, and did that without you having to set that yourself. That is what this encapsulation is about...


As for your question "as asked", whether your implementation could do read a salary more elegantly, you could of course...

void setSalary(istream & in) {
  in >> salary;
}

...and...

cout << "Enter salary:" << endl;
e.setSalary( cin );

And because that's properly encapsuled, you could add sanity checks, change the type of salary to BigNum, or start support reading in hieratic.

DevSolar
  • 67,862
  • 21
  • 134
  • 209
0

THIS QUESTION IS A GENERAL ANSWER WILL COVER YOU QUESTION

So in OOP, making and using only the copy or the OBJECT is a core concept, the way you are making a single copy OBJECT of a class is a standard one, but have you ever thought about MAKING AN ARRAY OF TYPE "YOUR CLASS's Objects"? because in your case the obj will only hold only one copy of Employee but if you use a data structure e.g "Array" for storing a number of objects then it will be a lot easier let me clarify for you

EXAMPLE

int a, b, c, d, e, f, g ... z;

assigning all of these alphabets a value will be a huge pain but wait you can have a data structure for it.

int arrayOfAlphabets[26];

now you want to assign values? (you can assign it dynamically but this is just an example to show you that you can just control the assigning of a variable with just a counter)

for(int x = 0 ; x < 26; x++){
  arryOfAlphabets[x] = someNumber
}

Want to get Values? you can get values dynamically but this is just an example to show you that you can just control the setting & getting of a variable with just a counter)

for(int x = 0; x < 26; x++){
cout << "Value of Alphabet" + x + " is: " + arrayOfAlphabets[x] << endl;
}

now you have seen the example that making a data structure specific data type can remove a lot of pain of yours now keep in mind for the sake of understanding,

YOUR CLASS IS DATATYPE THAT YOU CREATED

    class Employee {
  private:
    // Private attribute
    int salary;

  public:
    // Setter
    void setSalary(int s) {
      salary = s;
    }
    // Getter
    int getSalary() {
      return salary;
    }
};

and in main what you can do is

int main(){
  Employee emp[5]; 
  int tempSalery;
    for( int i=0; i<5; i++ )
    {
        cin >> tempSalery;
        emp[i].setSalery(tempSalery);
    }
}

now to print out your result

for( int i=0; i<5; i++ ){
        cout << "Employee " + x + "Salery is: " + emp[i].getSalery() << endl;
    }
Ali
  • 519
  • 4
  • 13