1

My apologies for the bad title, I'm self-taught c++ newbie.

I'd coding the usual example of a Company class along with Employess class and Person class.

Basically my code should create a company, add employees to that and print the full list of employees.

Here's a short version of what I did. It looks quite long, but the isse is just in a couple of lines. All the others are fine, but I needed them so to make the code work.

// g++ test.cpp  -o exe.x && ./exe.x

#include <iostream>
#include <vector>


class Person {
    private:
        std::string name;
        std::string surname;

    public:
        Person() {};
        Person(const std::string n, const std::string m) : name(n), surname(m) {}
        ~Person() {};

    friend std::ostream & operator<<(std::ostream & os, const Person & person) {
        return os << person.name << " " << person.surname;
    }
};

class Employee: public Person {
    private:
        size_t salary;

    public:
        Employee(const Person p, const size_t w) {
            salary = w;
        }
        ~Employee() {};

    friend std::ostream & operator<<(std::ostream & os, const Employee & employee) {
        std::cout << employee;                                                  // this should call std::cout from Person
        return os << ", " << employee.salary;                                   // this should also print the salary as additional info
    }
};

class Company {

    private:
        std::string companyName;
        size_t maxNumberEmployees;
        std::vector<Employee> employees;

    public:
        Company(const std::string n, const size_t p) {
            companyName = n;
            maxNumberEmployees = p;
        }

        ~Company() {
            employees.clear();
        }

        void addEmployee(const Employee employee) {
            if (employees.size() < maxNumberEmployees) {
                employees.push_back(employee);
            }
            return;
        }

        void listEmployees() const {
            for (Employee employee : employees) {
                std::cout << employee << std::endl;
            }
            return;
        }
};


int main ( int argc, char* argv[] ) {

    Company com("CompanyName", 15);

    Person per("Name1", "Surname1");
    Employee imp(per, 2000);
    com.addEmployee(imp);

    com.listEmployees();

    return 0;
}

Employee is a sub-class of Person. In the latter, I defined how the std::cout command should print an object of type Person.

When I give std::cout for an Employee object, I want my code to call the std::cout of the Person class, with an additional info defined in the Employee class.

I put two comments in the lines in which I have these troubles. The code gets stuck if I run it this way. There's definetely something wrong in that std::cout << employee, like if it were a recursive function. But I don't know how to fix that in the way I want it.

Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108

1 Answers1

1

In general, you need to add a layer of indirection. In Person, add a virtual function to print the contents:

virtual void print(std::ostream& os) {
    os << whatever;
}

and in Employee, override it and call the base version:

void print(std::ostream& os) {
    Person::print(os);
    os << whatever;
}

And, finally, get rid of the stream inserter for Employee and write a stream inserter for Person that calls this function:

std::ostream& operator<<(std::ostream& os, const Person& person) {
    person.print(os);
    return os;
}

That's a broader solution than you asked for, but it's more robust. To literally do what you asked for, just explicitly call the << operator with the Person subobject:

friend std::ostream & operator<<(std::ostream & os, const Employee & employee) {
    std::cout << static_cast<const Person&>(employee);
    return os << ", " << employee.salary;  
}
Ted Lyngmo
  • 93,841
  • 5
  • 60
  • 108
Pete Becker
  • 74,985
  • 8
  • 76
  • 165
  • Thank you for your reply. I actually prefer the latest hint, since I don't want to add other functions. I must say I already found that cast you suggested. I don't understand why it doesn't work in this case. Have you tested that in the code I wrote here? – Jimmy Scionti Mar 02 '20 at 15:31
  • @JimmyScionti It should be cast to `const Person&`, updated. – Ted Lyngmo Mar 02 '20 at 16:13
  • @TedLyngmo, I still get an empty output before the salary value, like: " , , 2000". I wonder how can c++ know how to perform that cast. – Jimmy Scionti Mar 02 '20 at 16:21
  • That's because you have bugs. You never actually set the `name` and `surname` members in `Employee`. Try making your constructor like this instead: `Employee(const std::string& n, const std::string& m, size_t w) : Person(n,m), salary(w) {}` – Ted Lyngmo Mar 02 '20 at 16:25
  • @JimmyScionti -- calling the base version of `operator<<` explicitly from the derived version works for now, but when you're writing real object-oriented code you'll be using `Person&` to talk about objects of various derived types, and you'll need the virtual function. – Pete Becker Mar 02 '20 at 16:29
  • @TedLyngmo Uhm... So if, for whatever reason, I need to change Person (for example, pretend I'd want to add a middle_name variable, or switch the ordering of name and surname), I should change the constructor(s) of this (and all the others) derived class Employee? – Jimmy Scionti Mar 02 '20 at 16:29
  • @JimmyScionti -- if you need to change the arguments to the constructor of the base type, all derived types have to deal with that change. So, yes, if you add an argument for a middle name to the constructor then the constructor for `Employee` has to supply a value for that argument. I suggest getting rid of the default constructor for `Person`; that way, if you change the number or types of arguments to the remaining constructor, derived types won't compile and you'll have a built-in reminder that you need to change their constructors, too. – Pete Becker Mar 02 '20 at 16:35
  • @JimmyScionti Or you can keep your current version where you supply a `Person` as argument to `Employee` - but you must then pass that on to `Person`s copy constructor: `Employee(const Person& p, size_t w) : Person(p), salary(w) {}`. As it is in your current code, `p` is just left unused which is why `name` and `surname` is never set. – Ted Lyngmo Mar 02 '20 at 16:38
  • @PeteBecker that's bad. I mean, it's a bit of a pain, isn't it? This is a quite simple example, but very long codes might have many derived classes from a single class, or even a very long gerarchy of sub-classes. I thought it was possible to avoid editing all the "daughter classes". By the way, I'm not sure about the use of "virtual", that's also why I didn't want to go for the other solution you suggested... Apologies, still learning... – Jimmy Scionti Mar 02 '20 at 16:41
  • @JimmyScionti The problem of adding an extra field to all derived classes if a base class changes its constructor is usually not a big issue. Also note what I wrote above that you _can_ keep your current construct (although it's a bit unconventional) - but you must also _use_ the `Person` in the `Employee` constructor as I showed. – Ted Lyngmo Mar 02 '20 at 16:48
  • @JimmyScionti -- yes, it's a bit of a pain. That's why it's important to get the design of your base class right, or as close to right as you can, early on. – Pete Becker Mar 02 '20 at 16:56
  • 1
    @TedLyngmo "it's a bit unconventional" that's one of the thing I can't learn on my own: how things are commonly used. :\ PeteBecker: that might never be my case. Thank you a lot to both of you. I'm confident I'll come back to this thread quite often, there's some juice I still need to squeeze in what the both of you wrote here. – Jimmy Scionti Mar 02 '20 at 17:03