3

次の2つのインターフェイスがありますが、これらは継承階層の一部ではありません。次に、2つの具体的なクラスがあります。1つはもう1つから派生しています。

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は、2つの異なるチームによって作成され、2つの異なるプロジェクトに属しています。チームは、共有される操作の共通メソッドシグネチャについて合意しました。実装クラスで継承を使用して、同じ実装の部分を再利用しようとしました。

4

4 に答える 4

5

問題は、ここで複数の継承が行われているという事実にあります...FastCarは から派生していますが、基本クラスのCarのバージョンはではなく をオーバーライドするだけです。したがって、から派生しているため、ある時点で、純粋仮想抽象メソッドもオーバーライドする関数を定義する必要があります...継承されたものは、そのクラスから継承されないため、 自動的にオーバーライドされません。とは、個別にオーバーライドする必要がある 2 つの異なる純粋抽象仮想関数です。callのインターフェースを継承したい場合drive()CarICar::drive()IFastCar::drive()FastCarIFastCarFastCarIFastCar::drive()Car::drive()IFastCare::drive()IFastCar::drive()ICar::drive()IFastCar::drive()Car::drive()のバージョン内で行ったように、FastCar::drive()その基本クラスのバージョンのdrive().

于 2011-10-12T15:52:15.857 に答える
3

問題は、Car実装されていないIFastCar::driveだけICar::driveです。最初の設計上の問題は、なぜ がインターフェイスをIFastCar拡張せず、同じ操作を再定義しないのかということです。ICar

何らかの理由でそれがオプションでない場合、実際にできる最も簡単なことは、リクエストをメソッドに転送することによって実際に実装IFastCar::drive()することです。FastCarCar::drive

void FastCar::drive() {
   Car::drive();
}
于 2011-10-12T15:59:11.320 に答える
2

ここでの継承の使用virtualは偽りであり、ニシンです。 virtual継承は、共通の基本クラスから一度だけ派生するために使用されます。一致するメンバー署名の宣言を 1 つだけ持ち込まないでください。

したがって、FastCar具象クラスには実際には 2 つではなく 3 つのメンバーがあります。

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

この 2 つ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派生させることができます。そのため、継承が必要になります。ひし形の上部のすぐ下のポイントで継承を適用​​することを忘れないでください。FastCarCarvirtualvirtual

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;
}

これにより、実装の継承という通常の目標が達成されます。つまり、複数の具象クラスで共有できる共通のコード セットを 1 つだけ保持することで、保守が容易になります。

于 2011-10-12T16:30:41.507 に答える
0

ICar と IFastCar は常に車であるため、ICar から派生した IFastCar を使用してから、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;
}
于 2011-10-12T16:05:37.593 に答える