Как заставить производный класс использовать базовую реализацию для удовлетворения интерфейса - PullRequest
3 голосов
/ 12 октября 2011

У меня есть два следующих интерфейса, которые не являются частью иерархии наследования. Затем у меня есть два конкретных класса, один из которых является производным от другого.

class ICar {
    public:
    virtual void drive() = 0;
};

class IFastCar {
    public:
    virtual void drive() = 0;
    virtual void driveFast() = 0;
};

class Car : public virtual ICar {
    public:
    void drive() {};
};

class FastCar : public Car, public virtual IFastCar {
    public:
    void driveFast() {};
};

int main()
{
    FastCar fc;
    fc.drive();
    fc.driveFast();
    return 0;
}

При компиляции я получаю следующую ошибку:

error: cannot declare variable `fc' to be of type `FastCar'
error:   because the following virtual functions are abstract:
error:  virtual void IFastCar::drive()

Моя программа будет работать, если я напишу в FastCar функцию для делегирования функции drive () базовому классу

void drive() { Car::drive(); }

Можно ли скомпилировать FastCar без написания методов для делегирования базовому классу?

Примечание: ICar и IFastCar создаются двумя разными командами и находятся в двух разных проектах. Команды согласовали общие методы подписи для операций, которые являются общими. Я использовал наследование в классах реализации, чтобы попытаться повторно использовать части реализации, которые являются одинаковыми.

Ответы [ 4 ]

5 голосов
/ 12 октября 2011

Проблема заключается в том, что здесь происходит множественное наследование ... хотя FastCar получено из Car, версия drive() в базовом классе Car только переопределяет ICar::drive() не IFastCar::drive(). Таким образом, поскольку вы выводите FastCar из IFastCar, вам необходимо в какой-то момент определить в FastCar функцию, которая также переопределяет чистый виртуальный абстрактный метод IFastCar::drive() ... Унаследованный Car::drive() будет не переопределяет IFastCare::drive() автоматически, так как не наследуется от этого класса. IFastCar::drive() и ICar::drive() - это две разные чисто абстрактные виртуальные функции, которые необходимо переопределять отдельно. Если вы хотите, чтобы интерфейс для IFastCar::drive() вызывал вашу унаследованную функцию Car::drive(), то вам нужно будет делегировать эту унаследованную функцию, как вы это делали в версии FastCar::drive(), которая специально вызывает ее версию базового класса drive().

3 голосов
/ 12 октября 2011

Проблема в том, что Car не реализует IFastCar::drive, а только ICar::drive.Первый вопрос разработки заключается в том, почему IFastCar не расширяет интерфейс ICar и переопределяет ту же операцию?

Если по какой-то причине это не вариант, то простейшая вещь, которую вы можете сделать, - это на самом деле реализоватьIFastCar::drive() in FastCar, перенаправив запрос методу Car::drive:

void FastCar::drive() {
   Car::drive();
}
2 голосов
/ 12 октября 2011

Ваше использование virtual наследования здесь спурсистское и представляет собой красную сельдь.virtual наследование используется для выведения из общих базовых классов только один раз; не для ввода только одной декларации для соответствующих подписей членов.

Итак, ваш конкретный класс FastCar на самом деле имеет 3 члена, а не 2:

virtual void ICar::drive()
virtual void IFastCar::drive()
virtual void IFastCar::driveFast()

два разных drive() кажутся взаимосвязанными, поэтому ваш дизайн кажется ошибочным.Вам не нужно наследование virtual - вы, вероятно, хотите, чтобы IFastCar происходило от ICar.

class ICar {
    public:
    virtual void drive() = 0;
};

class IFastCar : public ICar {
    public:
    virtual void driveFast() = 0;
};

class Car : public virtual ICar {
    public:
    void drive() {};
};

class FastCar : public IFastCar {
    public:
    void drive() {};
    void driveFast() {};
};

int main()
{
    FastCar fc;
    fc.drive();
    fc.driveFast();
    return 0;
}

Если вы тогда хотели, чтобы Car реализовал некоторые базовые функции, которые FastCarнанять, тогда вы можете получить FastCar из Car, а , что - вот почему вы хотите virtual наследование.Просто не забудьте применить virtual наследование в точке чуть ниже вершины ромба:

class ICar {
    public:
    virtual void drive() = 0;
};

class IFastCar : virtual public ICar {
    public:
    virtual void driveFast() = 0;
};

class Car : public virtual ICar {
    public:
    void drive() {};
};

class FastCar : public IFastCar, public Car {
    public:
    void driveFast() {};
};

int main()
{
    FastCar fc;
    fc.drive();
    fc.driveFast();

    ICar* fc_a = new FastCar;
    fc_a->drive();  // invokes Car::drive() via domination

    return 0;
}

Если вы скомпилируете и запустите приведенный выше код, вы получите желаемое поведение, но за счет компиляторапредупреждение.На MSVC10 это выглядит так:

1>main.cpp(19): warning C4250: 'FastCar' : inherits 'Car::Car::drive' via dominance
1>          main.cpp(13) : see declaration of 'Car::drive'

Это предупреждение о том, что ваша архитектура наследования реализации может быть испорчена.И на самом деле в большинстве случаев это, вероятно, так (хотя в этом случае нет).Это может очень быстро сбить с толку, особенно программисту, который уже несколько лет пытается выяснить ваш код.Чтобы устранить эту путаницу и избежать всех предупреждений компилятора, я предпочитаю (как правило) делегирование дочернему классу, а не наследованию реализации.

class ICar {
    public:
    virtual void drive() = 0;
};

class IFastCar : virtual public ICar {
    public:
    virtual void driveFast() = 0;
};

class ICarImpl 
{
public:
    void drive_impl() {};
};

class IFastCarImpl
{
public :
    void driveFast_impl() {};
};

class Car : public virtual ICar, protected ICarImpl {
    public:
    void drive() { drive_impl(); }
};

class FastCar : public IFastCar, protected ICarImpl, protected IFastCarImpl {
    public:
    void driveFast() { driveFast_impl(); }
    void drive() { drive_impl(); }
};

int main()
{
    FastCar fc;
    fc.drive();
    fc.driveFast();

    ICar* fc_a = new FastCar;
    fc_a->drive();  

    return 0;
}

Это выполняет обычную задачунаследования реализации - хранение только одного общего набора кода, который может совместно использоваться несколькими конкретными классами, что упрощает обслуживание.

0 голосов
/ 12 октября 2011

Поскольку ICar и IFastCar всегда являются автомобилями, вы можете использовать IFastCar, производный от ICar, а затем Car и FastCar, реализованные как вы.

class ICar {
    public:
    virtual void drive() = 0;
};

class IFastCar : ICar 
{
    public:
    virtual void drive() = 0;
    virtual void driveFast() = 0;
};

class Car : ICar {
    public:
    void drive()
    {
        cout << "driving a normal car" << endl;
    }
};

class FastCar : IFastCar
{
    public:
    void drive()
    {
        cout << "driving a fast car the normal way" << endl;
    }

    void driveFast()
    {
        cout << "driving a fast car at top speed" << endl;
    }
};

int main()
{
    FastCar fc;

    fc.drive();
    fc.driveFast();
    return 0;
}
...