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
  • 301,070
  • 26
  • 186
  • 335
Roxox
  • 99
  • 7
  • What error or errors are you getting? – Anon Mail Jan 11 '22 at 18:26
  • 2
    If the second operand of `<<` is an `Employee&`, the only option for the compiler is the implementation of `operator<<` that takes `Employee const&` as second parameter. For all 3 objects you print this is applies... – fabian Jan 11 '22 at 18:26
  • Give all your classes a virtual function `std::ostream put(std::ostream & os) const;` and use that one in the operator overload for `Employee`. That should be sufficient. – πάντα ῥεῖ Jan 11 '22 at 18:27
  • One thing, make the destructor virtual in the base class, else you will leak memory when you attempt to delete your pointers `t` and `ee`. – Bathsheba Jan 11 '22 at 18:29
  • @πάνταῥεῖ OP may be at an experience level where that could do with being written out longhand as an answer. – Bathsheba Jan 11 '22 at 18:30
  • 1
    @Bathsheba l'm just lazy ATM ;-P – πάντα ῥεῖ Jan 11 '22 at 18:32
  • 1
    See if you can apply [Print Derived class object using a vector of Base class pointers referring this object](https://stackoverflow.com/questions/35114512/print-derived-class-object-using-a-vector-of-base-class-pointers-referring-this). What Sam demonstrates in his answer is essentially the same approach described by πάνταῥεῖ. – user4581301 Jan 11 '22 at 18:34
  • Thanks for all comments but can anyone provide valid solution for this problem? I still dont know how to fix it. – Roxox Jan 11 '22 at 18:35
  • @Roxox read all of _@Turtlefight_'s answer thoroughly. – πάντα ῥεῖ Jan 11 '22 at 18:45

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
  • 9,420
  • 2
  • 23
  • 40
  • 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 (<<)? Explain and create an interface class (as we did in the course) and provide functionality so all employees are printable to an ostream using the overloaded insertion operator." How can I solve this problem using this way. – 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
  • 301,070
  • 26
  • 186
  • 335
  • @fabian Yes it is a referenced type but in the context of the question this does not matter. – Vlad from Moscow Jan 11 '22 at 18:43
  • 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 (<<)? Explain and create an interface class (as we did in the course) and provide functionality so all employees are printable to an ostream using the overloaded insertion operator." How can I solve this problem using this way. – Roxox Jan 11 '22 at 18:44
  • @Roxox I am sorry but I do not know what your instructor means. – Vlad from Moscow Jan 11 '22 at 18:46
  • What is wrong with this overloaded insertion operator (<<)? Explain *The right overload is only selected when the type is statically known. This is rarely the case when dealing with a class hierarchy with virtual functions.* and create an interface class (as we did in the course) *we don't know what you did in the course* and provide functionality so all employees are printable to an ostream using the overloaded insertion operator *this is what the solution does, in a standard way. If you want to do it the way you did it in the course, you ought to teach us the relevant part of the course.* – n. m. could be an AI Jan 11 '22 at 19:45