Search code examples
c++constructorencapsulation

Encapsulation along with constructors


I want the int Medals which I put private to be unable to have negative values, but I don't know how to implement that encapsulation along with constructors. I made it so that each athlete type inherits the Athlete constructor but I don't know where to call the setMedals function for it to work.

#include <iostream>
#include <string>
#include <vector>
#include <algorithm>
#include <functional>
using namespace std;

class Athlete {
private:
    int Medals;
public:
    string Name;
    void setMedals(int newMedals) {
        if (newMedals >= 0)
            Medals = newMedals;
    }
    int getMedals() const{
        return Medals;
    }

    virtual string getDescription() = 0;
    Athlete(string _Name, int _Medals) : Name(_Name), Medals(_Medals) {}
};

class Footballer : public Athlete {
public:
    string getDescription() {
        return "Footballer ";
    }
    Footballer(string Name, int Medals) :  Athlete(Name, Medals) {}
};

class Basketballer : public Athlete {
public:
    string getDescription() {
        return "Basketballer ";
    }
    Basketballer(string Name, int Medals) : Athlete(Name, Medals) {}

};

ostream& operator <<(ostream& output, vector<Athlete*> athletes) {
    for (int i = 0; i < athletes.size(); i++) {
        output << athletes[i]->getDescription() << " " << athletes[i]->Name << ": " << athletes[i]->getMedals() << " Medals" << endl;
    }
    return output;
}

void printAthletes(vector<Athlete*> athletes) {
    sort(athletes.begin(), athletes.end(), [](Athlete* a, Athlete* b) {
        return a->getMedals() > b->getMedals(); });

        cout << athletes;
}

int main() {    
    Footballer Andrew("Andrew", 3), Jack("Jack", 4);
    Basketballer David("David", 5), Rob("Rob", 1);
    vector<Athlete*> Athlete = { &Andrew, &Jack, &David, &Rob };
    printAthletes(Athlete);

    
    return 0;
}

I hope you understand my question cause I don't know how else to phrase it.


Solution

  • tl;dr: Calling non-virtual function inside a constructor is generally fine, although I'd pay attention to it when dealing with larger objects. So, sth like this should do:

    class Athlete
    {
    private:
      unsigned medals{0};
    public:
      string name;
      void setMedals(int m) //or just use unsigned...
      {
        if (m >= 0)
          medals = m;
      }
      unsigned getMedals() const{return medals;}
    
      virtual string getDescription() = 0; //should be const maybe?
      Athlete(string n, int m) : name(move(n))
      {
        setMedals(m); 
      }
    };
    

    As for the extended answer:

    The first answer, i.e. to use unsigned int is good enough. However, one may wonder what the whole purpose of getters and setters is, if they are literally a pass-through to access the variable, thus no real encapsulation is there.

    For that reason, one may simply consider sth like this (interface is simplified for brevity):

    struct Athlete
    {
      unsigned medals;
    };
    

    If some sort of input validation/processing is needed, e.g. medals cannot exceed 10, one can consider using a setter and getter, e.g.

    class Athlete
    {
    public:
      explicit Athlete(unsigned m)
      : medals {clamp(m, 0, 10)}
      //or throw from constructor
      //depedns what one wants really
      {}
      unsigned getMedals() const { return medals; }
      void setMedals(unsigned m) { medals = clamp(m, 0, 10); }
      //again, you may throw or do anything else
      //clamping is just an example
    private:
      unsigned medals;
    };
    

    However, a question about object's responsibility arises here. Maybe it's not the Athlete that should care about the number of medals (or whatever the value represents), but the Medals variable iself should be distinct type maintaining its own invariance. Should one decide to chase this approach, it can look like this:

    template <typename T, T LO, T HI>
    class LimitedInt
    {
    //all the needed operations
    };
    
    struct Athlete
    {
      using Medals = LimitedInt<unsigned, 0, 10>;
      Medals medals;
    };
    
    

    Unfortunately, no easy answers here which one is better or worse, it depends on various factors, let alone code style and frameworks used are one of them, e.g. QT uses getters and setters extensively by convention.