Does downcasting defeat the purpose of polymorphism?

前端 未结 4 805
暖寄归人
暖寄归人 2021-01-01 16:52

I encountered a question today, found here, which raised this question for me.

Here\'s a pseudo-code example of what I\'m getting at:

class Car{
publ         


        
相关标签:
4条回答
  • 2021-01-01 17:18

    If you care about the retractability of the roof, you could have a ConvertibleCar abstract base class between Car and Lamborghini in the inheritance chain :

    class Car {
      public :
        virtual int goFast() = 0;
    };
    
    class ConvertibleCar : public virtual Car {
      public :
        virtual void retractTheRoof() = 0;
    };
    
    class FordFocus : public Car {
      public :
        int goFast() { return 35; };
    };
    
    class Lamborghini : public ConvertibleCar {
        bool roof;
      public :
        int goFast() { return -1/0; /* crash */ };
        void retractTheRoof() { roof = 0; };
    };
    

    If the RichGuy class can't keep track of all different kinds of cars separately, you can still use dynamic_cast to figure out if a certain car is of a certain type :

    ConvertibleCar* convertible = dynamic_cast<ConvertibleCar*>(cars[i]);
    if (convertible) {
        convertible->retractTheRoof();
    };
    

    Note that this scales quite well with different car types (ConvertibleCar, AllTerrainCar, SportsCar, ...), where the same car can inherit from 0 or more of these types. A Lamborghini would probably derive from both ConvertibleCar and SportsCar :

    class Lamborghini : public SportsCar, public ConvertibleCar {
        // ...
    };
    
    0 讨论(0)
  • 2021-01-01 17:20

    Now if there are more than one type of cars which is retractable, say such cars are CarA, CarB, and CarC (in addition to Lamborghini), then are you going to write this:

    if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
        lambo->retractTheRoof();
    }
    else if(CarA * pCarA = dynamic_cast<CarA*>(cars[i])) {
        pCarA->retractTheRoof();
    }
    else if(CarB * pCarB = dynamic_cast<CarB*>(cars[i])) {
        pCarB->retractTheRoof();
    }
    else if(CarC * pCarC = dynamic_cast<CarC*>(cars[i])) {
        pCarC->retractTheRoof();
    }
    

    So a better design in such cases would be this: add an interface called IRetractable and derive from it as well:

    struct IRetractable 
    {
       virtual void retractTheRoof() = 0;
    };
    
    class Lamborghini : public Car, public IRetractable {
       //...
    };
    
    class CarA : public Car, public IRetractable {
       //...
    };
    class CarB : public Car, public IRetractable { 
       //...
    };
    class CarC : public Car, public IRetractable {
       //...
    }; 
    

    Then you can simply write this:

    if(IRetractable *retractable =  dynamic_cast<IRetractable *>(cars[i])) 
    {
        retractable->retractTheRoof(); //Call polymorphically!
    }
    

    Cool? Isn't it?

    Online demo : http://www.ideone.com/1vVId

    Of course, this still uses dynamic_cast, but the important point here is that you're playing with interfaces only, no need to mention concrete class anywhere. In other words, the design still makes use of runtime-polymorphism as much as possible. This is one of the principle of Design Patterns:

    "Program to an 'interface', not an 'implementation'." (Gang of Four 1995:18)

    Also, see this:

    • What does it mean to "program to an interface"?

    Other important point is that you must make the destructor of Car (base class) virtual:

    class Car{
    public:
        virtual ~Car() {} //important : virtual destructor
        virtual int goFast() = 0;
    };
    

    Its imporant because you're maintaining a vector of Car*, that means, later on you would like to delete the instances through the base class pointer, for which you need to make ~Car() a virtual destructor, otherwise delete car[i] would invoke undefined behaviour.

    0 讨论(0)
  • 2021-01-01 17:20

    Instead of checking for "Lamborghini" in this module, because I know for a fact that Lamborghini is not the only car maker that makes cars with retractable roofs.

    Then you don't need dynamic-casting at all. That's how it should have been done.

    class Car{
    public:
        virtual int goFast() = 0;
        virtual void retractTheRoof(){ /*no-op*/}; // default
    };
    
    class Lamborghini : public Car {
        bool roof;
    public:
        int goFast(){
            return -1/0;  // crash 
        };
        void retractTheRoof(){ roof = 0;}; // specific
    };
    

    and then in the code instead of

    if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
        lambo->retractTheRoof();
    }
    

    do

    cars[i]->retractTheRoof();
    

    That's it.

    0 讨论(0)
  • 2021-01-01 17:39

    Yes, this is usually the better design case. You should only introduce a virtual function into the hierarchy if it makes sense for all derived classes.

    However, your MODEL enum is completely worthless- that's what dynamic_cast is actually for.

    if(Lamborghini* lambo = dynamic_cast<Lamborghini*>(cars[i])) {
        lambo->retractTheRoof();
    }
    
    0 讨论(0)
提交回复
热议问题