0

I was trying to learn operator overloading in C++ PL. I made an exercise shown below. What I want to do is overload << operator for each derived class and use it on my main. But whenever I do it, its only working for Base class. What is the problem here?

Class Employee:

class Employee {
public:
    string name;
    int id;
    int exp_level;
    double salary;

    Employee() {
        this->name = "";
        this->id = 0;
        this->exp_level = 0;
        this->salary = 0.0;
    }

    ~Employee() {
        //WTF
    }

    virtual void calculateSalary() {
        //CODE
    }
    
    virtual void registerX() {
        //CODE
    }

    friend ostream& operator<<(ostream& os, const Employee& e) {
        os << e.name << " " << e.exp_level << " " << e.id << " " << e.salary << endl;
        return os;
    }
};

Class Technical:

class Technical : public Employee {
public:
    string profession;

    Technical() {
        this->profession = "";
    }

    ~Technical() {

    }

    virtual void calculateSalary() {
        //CODE
    }

    virtual void registerX() {
        //CODE
    }

    friend ostream& operator<<(ostream& os, const Technical& e) {
        os << e.name << " " << e.exp_level << " " << e.id << " " << e.salary << "Technical" << endl;
        return os;
    }
};

Class Engineer:

class Engineer : public Employee {
public:
    Engineer() {

    }

    ~Engineer() {

    }

    virtual void calculateSalary() {
        //CODE
    }

    virtual void registerX() {
        //CODE
    }

    friend ostream& operator<<(ostream& os, const Engineer& e) {
        os << e.name << " " << e.exp_level << " " << e.id << " " << e.salary << "Engineer" << endl;
        return os;
    }
};

Main Method:

int main()
{
    Employee* e = new Employee();
    Employee* t = new Technical();
    Employee* ee = new Engineer();

    cout << *e << endl;
    cout << *t << endl;
    cout << *ee << endl;    
}    

Output:

 0 0 0

 0 0 0

 0 0 0
Vlad from Moscow
  • 265,791
  • 20
  • 170
  • 303
Roxox
  • 99
  • 6

2 Answers2

3

C++ chooses the best overload based on the static type's of the function arguments, and because the type is Employee in this case, the Employee's operator<< gets called.

If you want it to call the correct version when you have a static type pointer / reference to it that doesn't match it's dynamic type you'll have to use a virtual function or use dynamic_casts / typeid to check for the concrete runtime type (virtual functions are the cleanest approach imho)

Example: godbolt

class Employee {
public:
    virtual ~Employee() = default;

    friend std::ostream& operator<<(std::ostream& os, const Employee& e) {
        return e.put(os);
    }

protected:
    virtual std::ostream& put(std::ostream& os) const {
        os << "Employee!";
        return os;
    }
};

class Technical : public Employee {
protected:
    std::ostream& put(std::ostream& os) const override {
        os << "Technical Employee!";
        return os;
    }
};

class Engineer : public Employee {
protected:
    std::ostream& put(std::ostream& os) const override {
        os << "Engineer Employee!";
        return os;
    }
};

int main() {
    Employee* e = new Employee();
    Employee* t = new Technical();
    Employee* ee = new Engineer();

    std::cout << *e << std::endl;
    std::cout << *t << std::endl;
    std::cout << *ee << std::endl;

    delete ee;
    delete t;
    delete e; 
}

would result in:

Employee!
Technical Employee!
Engineer Employee!

Also keep in mind that once you have at least 1 virtual function in a class then the destructor should almost definitely be also virtual.

e.g.:

Employee* e = new Technical();
delete e;

would only call ~Employee(), but not ~Technical(), if the destructor is not virtual.

So whenever you want to delete an object through a pointer to one of it's base-classes the destructor needs to be virtual, otherwise it's undefined behavior.

Turtlefight
  • 7,034
  • 1
  • 18
  • 37
  • Thanks for answer. It works now but I want to learn another solution. My Instructor commented to me "What is wrong with this overloaded insertion operator (< – Roxox Jan 11 '22 at 18:44
  • @Roxox that's basically the solution i posted, just with the "put" function extracted into an interface class - e.g. something like this should satisfy it: [godbolt](https://godbolt.org/z/sWfnGaMnK) - this allows every class that implements the `Printable` interface to be be used with streams. – Turtlefight Jan 11 '22 at 18:49
2

Due to these declarations

Employee* e = new Employee();
Employee* t = new Technical();
Employee* ee = new Engineer();

the static type of the expressions *e, *t, *ee is Employee &. So the operator

friend ostream& operator<<(ostream& os, const Employee& e)

is called for all three objects.

A simple way to make the friend operator << "virtual" is to define in each class a virtual function like for example

virtual std::ostream & out( std::ostream & ) const;

and define the (only) friend operator like

friend ostream& operator<<(ostream& os, const Employee& e)
{
    return e.out( os );
}

The virtual function out need to be redefined in each derived class.

Vlad from Moscow
  • 265,791
  • 20
  • 170
  • 303