2

Consider the following code (also available at C++ Shell).

Basically, we have an Employee base class with FullTime and PartTime subclasses. The organization class HAS a list of Employees (who could be Fulltime or PartTime).

The problem arises when I want to define a getEmployee(int) method in the Organization class. What type of Employee should it return? If it returns a Employee*:

  1. first of all, is it safe to return a pointer? I assume that its address is permanent, because it will send the address of that element in the empList. right?
  2. When I cast Employee* to a FullTimeEmployee*, obviuosly I lose the grade field. How can I resolve this?

According to the answer Here, if I need to know "what class an object is, that usually indicates a design flaw...". Is it also the case here?

 #include <iostream>
    #include <string>
    #include <list>
    #include <algorithm>
    using namespace std;

    class Employee {
     public:
      int getId() { return id;}
       void setId( int id) {this->id = id;}
     protected:
      int id;

    };

    class PartTimeEmployee : public Employee {

    };

    class FullTimeEmployee : public Employee {
     public:
      int getGrade() {return grade;}
      void setGrade(int grade) {this->grade = grade;}
     private:
      int grade;
    };

    class Organization {
     public:    
      void addEmployee(Employee& e) { empList.push_back(e); }
      Employee* getEmployee(int id) { 
          for (std::list<Employee>::iterator it=empList.begin(); it!=empList.end(); ++it) {
              if(it->getId() == id) {return &(*it);}
          }
          return NULL; 
      }
     private:
      std::list<Employee> empList;
    };

    int main()
    {
      Employee e1;
      e1.setId(5);
      FullTimeEmployee *pFt1 = (FullTimeEmployee*) &e1; 
      pFt1->setGrade(1);                

      Organization org1;
      org1.addEmployee(*pFt1);
      FullTimeEmployee *pFt2 = (FullTimeEmployee*) org1.getEmployee(5);
      cout << pFt2->getId() << endl;
      cout << pFt2->getGrade() << endl;
    }
Community
  • 1
  • 1
towi_parallelism
  • 1,421
  • 1
  • 16
  • 38
  • 2
    You should be asking question 0, before 1 and 2. "Am I even storing `FullTimeEmployee` and `PartTimeEmployee` objects?" And the answer is no. You have a list of `Employee` objects, because you are storing them by value. [Slicing](http://en.wikipedia.org/wiki/Object_slicing) is occurring in the call to `empList.push_back(e)`. – Benjamin Lindley May 28 '15 at 15:17
  • And it is an error to create an Employee and then "cast" it into a FullTimeEmployee. Your e1 object dont have memmory allocated for the grade you want to set. – qPCR4vir May 28 '15 at 15:18
  • Yes, I know what I am doing is wrong. But what is the best way to design this? – towi_parallelism May 28 '15 at 15:22

3 Answers3

3

first of all, is it safe to return a pointer?

Yes. The object is in a list, so pointers and references to it remain valid until it's removed.

When I cast Employee* to a FullTimeEmployee*, obviuosly I lose the grade field.

You've already lost it before then - when you converted to Employee to put it in the list. If you want to store objects of different types (with a common base class), you'll have to store pointers, and adjust addEmployee to allow subtypes to be stored. Then you can use RTTI to distinguish between types after retrieving the pointer.

Remember to manage the lifetime of the objects (perhaps using smart pointers), and you'll probably need to give the base class a virtual destructor (both for correct deletion and to enable RTTI).

if I need to know "what class an object is, that usually indicates a design flaw...". Is it also the case here?

It's a matter of opinion, but I'd say yes. It seems simpler to me to add a property (or possibly a special value of "grade") to indicate the employment status than to faff around with inheritance and all its associated complications.

Mike Seymour
  • 249,747
  • 28
  • 448
  • 644
  • Thanks for your answer. Can you elaborate this more? "Remember to manage the lifetime of the objects (perhaps using smart pointers), and you'll probably need to give the base class a virtual destructor (both for correct deletion and to enable RTTI)." Also, it is not just one "grade" field here. What if you have temporary Employees, Contractors, Interns, and there are multiple fields shared between them? I think I need some sort of inheritance anyway.. – towi_parallelism May 28 '15 at 15:29
  • 1
    @TOWI_Parallelism: If you really do think you need inheritance, then you'll need objects of different types, with pointers to them in the list. You'll need to manage the lifetimes of those objects - perhaps by storing smart pointers like `std::unique_ptr`, or perhaps by coming up with some other scheme, which will probably involve creating them with `new` and destroying them with `delete`. You'll need a virtual destructor for this to work, and also to allow `dynamic_cast` to work (if you want to use that to figure out the real type behind an `Employee*` pointer). – Mike Seymour May 28 '15 at 15:39
  • Thanks. Changing the Organization methods and its list to deal with Employee* rather than Employee solved the problem. I have 2 complete answers to accept here! But one more question: I haven't worked with unique_ptrs. So, if I use them, it automatically deletes the object when it goes out of scope? – towi_parallelism May 28 '15 at 15:52
  • 1
    @TOWI_Parallelism: Yes, `unique_ptr` deletes its target when its destroyed. It's the simplest way to add a level of indirection when you need one. – Mike Seymour May 28 '15 at 15:53
3

Short answer: yes.

Long answer: maybe, but probably yes in your case.

The primary point of polymorphism is to make that kind of basic logic of asking, "What type of employee is this? Oh, it's a part-time employee? Do the right thing for part-time employees" an automatic, rather than manual process. Inheritance and virtual functions are one means of doing this automatically.

This can become a very useful concept to reduce maintenance overhead, as you can now add new employee types to your heart's content without constantly writing code checking what type of employee you are working with everywhere. You end up turning what would normally be a inextensible solution that would require you to manually extend the range of types you support in potentially many places intrusively in your codebase into something you can extend to the side non-intrusively without touching anything you've written so far.

The abstraction also helps you identify a common denominator interface that all types share. To think abstractly is generally focusing on what things should all do, not specific details of what they are. Focusing on that helps you concentrate on what things should be doing, and will give your codebase a greater degree of flexibility towards evolving changes as you can swap out what things are without breaking the code specifying what they should do.

A basic analogy I like to use is that if you wrote a boatload of code telling a robot to walk to various places, swapping the legs of that robot with wheels would break all of that code and you would have to rewrite it all. If, instead, the code is more abstract and telling the robot to merely go to various places, you can swap the robot's legs with a warp drive, jetpacks, and molecular teleporters and all of the code you painstakingly wrote would continue to work. A lot of the focus of software engineering is in the science (and perhaps art) of creating maintainable codebases. A large part of that is making the cost of changes cheaper, and for that you want more of these solutions which automatically adapt with your changes, and fewer of those which you have to manually adapt against your changes.

So on to your question:

first of all, is it safe to return a pointer? I assume that its address is permanent, because it will send the address of that element in the empList. right?

Pointers are perfectly fine here. Most of the dangers of using raw pointers are associated with memory management (ownership), not access. If your organization class is the one dealing with the memory management responsibility and Employee* is just used a handle to access a specific employee, it's perfectly fine. Using things like shared_ptr here would be overkill unless you need that kind of shared ownership.

When I cast Employee* to a FullTimeEmployee*, obviuosly I lose the grade field. How can I resolve this?

You generally either want the notion of a 'grade' to be applicable to all employees with either a virtual function or a non-virtual function and actual storage of the grade in your abstract Employee base class. Remember that polymorphism is about designing a public interface that all such employees would have in common regardless of whether they're part-time or full-time, regardless of their specific specialty, etc.

Now about your code:

std::list<Employee> empList;

This is a problem. You don't want to store Employee like this by value or else it's going to slice away the data required for a specific employee subclass. It needs to be Employee* or better, something like a shared_ptr or vector<unique_ptr<Employee>> to which you assign the memory address for specific employee subtype objects like new PartTimeEmployee.

We also need a virtual destructor in Employee so that when you or a smart pointer try to call delete through Employee*, it triggers the correct destruction logic for the specific type of employee we're pointing to like so:

   class Employee {
   public:
       virtual ~Employee() {}
       ...
   };

If I try to explain why informally, it's because the system doesn't know what Employee* is pointing to. So if you try to destroy that, it won't know what to destroy specifically unless there's a virtual destructor which tells it that the destructor logic can be different for different employee subtypes.

  • Thanks for the comprehensive answer. One point: you said "you ALSO need a virtual destructor". Even if I use unqiue_ptr? A small piece of could would complete the answer. However, I have resolved the issue by using Employee* as you mentioned – towi_parallelism May 28 '15 at 15:56
  • Yes, even for `unique_ptr`. The `unique_ptr` just handles the `delete` part for you so that you never have to worry about it. This can be especially helpful when you become exposed to exception handling where functions have hidden exits everywhere. Whenever you have an inheritance hierarchy where the subtypes differ in representation from one another, you generally want a virtual destructor. More advanced: you want either a virtual destructor or a protected non-virtual destructor, but I suggest just sticking to the virtual destructor whenever you use inheritance until you get more comfortable. –  May 28 '15 at 16:01
  • 1
    If I try to explain why informally, it's because the system doesn't know what `Employee*` is pointing to. So if you try to destroy that, it won't know what to destroy specifically unless there's a virtual destructor which tells it that the destructor logic can be different for different employee subtypes. This is all crudely speaking as data types don't even exist at the machine level, but ah... it's tough to balance trying to explain this stuff with technical accuracy. –  May 28 '15 at 16:04
  • About `unique_ptr` and its custom deleter, your original question is actually really complicated if you want to know the details. I would suggest, _"just add a virtual destructor to the base class or make it protected to get errors against attempts to delete through a base pointer!"_ But if you're interested in the gory details, http://stackoverflow.com/questions/8274451/well-how-does-the-custom-deleter-of-stdunique-ptr-work .More basic is just that your base class should be designed to work properly in any situation, not just with `unique_ptr` (even if it did work here). –  May 28 '15 at 16:10
  • Thanks for the complete answer. So, in conclusion, you suggest that changing the function argument and the entries from `Employee` to `Employee*`, `new`ing the objects based to their sub-type before passing to the functions. and defining a Virtual Destructor will be just fine. right? – towi_parallelism May 28 '15 at 16:23
  • Yes, though perhaps better to use `unique_ptr` for the actual container (your list of employees) to keep you out of memory management woes. The function to fetch a reference to an employee from an ID can just return a raw pointer as you have it, and you can continue to use null to indicate that an employee was not found. –  May 28 '15 at 16:24
  • Or hmm.. I'm not sure about `unique_ptr`. I'm not sure what is easier for someone new to this stuff to learn: to start with the really good and safe ways using smart pointers and work your way backwards to lower-level memory management concepts, or to work your way up to these things. I'd say it's up to you depending on what you find easier -- in production code you don't want to be sprinkling manual calls to `operator delete` everywhere, as that's a potential safety hazard and, when combined with exceptions, a very probable source of memory leaks. When you're just learning, I'm not sure. –  May 28 '15 at 16:30
  • It's a tough thing I find with C++11-14. I learned C++ in the early 90s and C before it and kind of gradually worked my way up to modern C++. Concepts like smart pointers are fantastic for actual, real-world production code where you can't afford to be leaking resources. But they might increase the learning curve for a novice quite a bit: _"Smart pointers? What's that? I'm still trying to understand how pointers work"_ -- this kind of dilemma. I think teaching/learning C++ is a little harder now, even though it is so much easier to use it now in production without getting into trouble. –  May 28 '15 at 16:36
  • Thanks again for your comments. I know a bit about C++ :) I was just wondering if I could solve my problem without dealing with heap and dynamic memory (which seems to be impossible, based on the answers). But I agree with you. I need a bit of time to read more about smart pointers. I will definitely go back to this post ASAP. – towi_parallelism May 28 '15 at 16:45
  • 1
    This is one of the awkward dilemmas with polymorphism and even really opaque kind of information hiding (compiler firewalls with pimpls, e.g.). To work with data types without having the complete information available about exactly what it is will often have you allocating stuff on the free store. There are tricks around this, but they work in very limited and special situations. More fruitful if you ever run into a performance hotspot is to create a fixed O(1) allocator (can start to rival stack performance with those). However, I don't recommend venturing into this realm of allocators, –  May 28 '15 at 16:47
  • ... alignment woes, etc. until you really have a profiler in hand and some really good measurements. –  May 28 '15 at 16:48
1

Do you have some detailed requirements about the location of Employee (base or derived) objects in memory?