0

I have a base class Auto_Part and several derived classes like class Battery, class Engine, class Frame, etc. I also have a grandchild derived class Car that inherits all of the derived parts classes.

My goal: I have an Inventory class and an inventory object that is a map<Auto_Part*, int> . The first value is the Auto_Part reference being stored, and the second value is how many are there available in the inventory. The user should be able to create a car object using parts available in the inventory. I want to iterate through this map and use the appropriate accessor method to get the data needed to create my car object.

Here is the function in question. I'm trying to use the MVC model so it is a Controller function.

void Controller::add_car_from_parts(){

//All variables used in all of the parts
std::string type, frame_type, category, color, bolt_pattern, tire_type, speed_rating,load_range, w_frame_type;
std::string name, fuel_type, grip_type, horn_type, display_color, seat_material, seat_warmers, door_material, butterfly_doors;
int part_number, cranking_amps, cold_cranking_amps, voltage,diameter,width,ratio, w_diameter,w_width, num_doors,
length,reserve_capacity_minutes,num_cylinders, steering_wheel_diameter, num_seats, display_length;
double price;
int quantity;

Dialogs::message("You will be shown the parts available in the inventory. Please write down the part number of the part you will add to you vehicle.", "PART NUMBER");

view.view_all_inventory();
part_number = view.pn_prompt();
//quantity = view.quantity_prompt();

for(auto x: inventory.get_inventory()){ //inventory is a map<Auto_Part*,int>

    if (x.first->get_part_number() == part_number){ // If part is in the inventory
        if (x.first->get_type() == "Battery"){// and if the part is a battery, auto_part function available in all auto_part classes including class Auto_Part
            name = x.first->get_name(); // auto_part function, available in all auto_part classes including class Auto_Part
            price = x.first->get_price(); // auto_part function, available in all auto_part classes including class Auto_Part
            cranking_amps = x.first->get_cranking_amps(); // battery function only
            cold_cranking_amps = x.first->get_cold_cranking_amps(); // battery function only
            voltage = x.first->get_voltage(); // battery function only
            reserve_capacity_minutes = x.first->get_reserve_capacity_minutes();  //battery function only
            inventory.remove_parts(x.first,1); // removes part from inventory, second value is quantity removed
        }

    }
}

}

So x.first->get_part_number(), x.first->get_type(), x.first->get_name(), and x.first->get_price() work fine because they are in the Auto_Part class as well as in all of the derived classes and so when iterating through it points to the appropriate get function. The first error is at the line cranking_amps = x.first->get_cranking_amps() and my compiler says "class Auto_Part has no get_cranking_amps() function, only battery function."

I thought that since the map is made of Auto_Part* and not just Auto_Part objects, it would reference the appropriate class. As in the x would change as it iterated through the map to a Battery object or an Engine object depending on what it was, and I could therefore use its unique functions. How do I call the derived class functions while iterating through this list of base class references? Surely, I don't need to create each of those derived class functions in the base class, right? That seems like it would defeat the purpose of having derived classes with unique functions.

Thank you for your time.

rbb091020
  • 37
  • 1
  • 1
  • 10
  • Unless my search-fu is weak, the question doesn't contain the word `virtual`. You're going to want to use __virtual functions__ for `get_cranking_amps()` and so on. Then you can call `base_class_pointer->get_cranking_amps()` and know that the correct function is being called. If you call it on a battery, it will get the correct number. If you call it on something else, it can return `0` or `-1` or some other value that makes sense to you. – Tim Randall Nov 30 '18 at 14:45
  • 4
    I strongly suggest against having `Car` inherit from `Battery`, `Engine` and so on. It might have **member data** of those types, but inheritance is **unhelpful** here. – Caleth Nov 30 '18 at 14:47
  • a poor solution would be to dynamic_cast to the type you think it is - handling the case when it isn't. a better solution might be to have a getproperties that would return a map of property/value pairs to be used by the caller who could pick and choose what he wanted. a better version of _that_ would be the visitor pattern. – davidbak Nov 30 '18 at 14:48
  • @Caleth - absolutely right - but i don't see how you got to that conclusion from the code given. engines and batteries deriving from an "auto_part" seems ok. – davidbak Nov 30 '18 at 14:49
  • @davidbak I wasn't talking about `AutoPart` as a base. Even then, I would be tempted to have inventory be a `vector>`, rather than a `vector` – Caleth Nov 30 '18 at 14:51
  • @davidbak: Caleth isn't surmising that from the code, it's stated right in the English text of the question. "I also have a grandchild derived class Car that inherits all of the derived parts classes." – Ben Voigt Nov 30 '18 at 14:53
  • @TimRandall I thought about that, but that would mean I have to add all of those unique functions to the Auto_Part class. Seems like a redundant solution and I thought there might be a better way. Thanks though! – rbb091020 Nov 30 '18 at 14:54
  • New programmer here: why would having Car inherit from all the parts be a bad idea? It seems logical that a car would be made of a bunch of parts. – rbb091020 Nov 30 '18 at 14:56
  • @rbb091020 actually, you only need to write two versions of `get_cranking_amps()`. One in the base class that returns `0`, and one in the `Battery` class that returns a nonzero number. The nice thing about virtual functions is that if you don't override the base class's function, that's the one that will be called. So every other type of part will return 0 without you needing to do any further work. – Tim Randall Nov 30 '18 at 14:57
  • @rbb091020 see [this Q&A](https://stackoverflow.com/questions/49002/prefer-composition-over-inheritance) – Caleth Nov 30 '18 at 15:05
  • @BenVoigt - oh jeez i missed that but Caleth is right: that's wrong. – davidbak Nov 30 '18 at 15:05
  • @rbb091020 - inheritance is a different relation than composition – davidbak Nov 30 '18 at 15:05

1 Answers1

0

The compiler has no idea that get_type() == "Battery" has any relationship to class Battery. You still have just a Auto_Part*.

If you want to use members not reachable through Auto_Part*, you have to do the type conversion yourself, using a cast:

auto superclass = x.first;
if (superclass->get_type() == "Battery") {
        name = superclass->get_name();
        price = superclass->get_price();
        Battery* subclass = static_cast<Battery*>(parent);
        cranking_amps = subclass->get_cranking_amps();
        cold_cranking_amps = subclass->get_cold_cranking_amps();
        voltage = subclass->get_voltage();
        reserve_capacity_minutes = subclass->get_reserve_capacity_minutes();
        inventory.remove_parts(superclass,1);
    }

But, when using the type system, you don't even need a get_type() function, you can use a runtime type check:

auto superclass = x.first;
auto as_battery = dynamic_cast<Battery*>(superclass);
if (as_battery) {
        name = superclass->get_name();a
        price = superclass->get_price();
        cranking_amps = as_battery->get_cranking_amps();
        cold_cranking_amps = as_battery->get_cold_cranking_amps();
        voltage = as_battery->get_voltage();
        reserve_capacity_minutes = as_battery->get_reserve_capacity_minutes();
        inventory.remove_parts(superclass,1);
    }

The dynamic_cast will evaluate to a null pointer if the runtime type doesn't match.

As @davidbak says, the Visitor pattern is designed to solve this problem in a more maintainable way (although there's a bit more code up front).

Ben Voigt
  • 277,958
  • 43
  • 419
  • 720
  • Thank you, this worked. Is there a reason why dynamic casting is not a preferred solution? Seems very useful. – rbb091020 Nov 30 '18 at 21:21
  • @rbb091020: Maintenance pitfalls. When you add a new subclass of `Auto_Part`, the code you need to update is scattered everywhere, and there's no help from the compiler. If you use the Visitor pattern, you only need to update one spot (the declaration of the Visitor interface) and the compiler will lead you (via errors) to every place that has type-dependent logic. – Ben Voigt Nov 30 '18 at 21:45