3

この単純なプログラムを考えてみましょう。

class Shape
{
public:
    virtual double getArea() = 0;

};

class Rectangle : public Shape
{
    int width;
    int height;

public:
    Rectangle( int w , int h ) :width(w) , height(h) {}

    double getArea() const
    {
        return width * height;
    }
};


int main() {

    Rectangle* r = new Rectangle(4,2);
}

この問題をコンパイルしようとすると、次のようになります。

 'Rectangle' : cannot instantiate abstract class

共変リターン型が許可されているのに、C ++でこれが許可されないのはなぜですか?もちろんRectangle::getArea、非定数関数にすることでプログラムを修正することはできますが、言語設計者が別の方法を決定した理由については興味があります。

編集

多くの人が、署名の違いについて回答の中で言及しています。しかし、そうです

class Shape
{
public:
    virtual BaseArea* getArea() = 0;

};

class Rectangle : public Shape
{
public:
    virtual RectangleArea* getArea();
};

しかし、C#が許可しない場合、C++はそれを許可するために邪魔になりません。

C ++は共変の戻り型をサポートします。これは、インターフェイスがaBaseArea*を返し、実装がaを返すことを期待している場合RectangleArea*、コントラクトが満たされているため、RectangleAreaがBaseAreaから派生している限り問題ありません。

同じように、変異関数のみを要求するインターフェースを満たす非変異関数を提供する実装ではありませんか?

4

6 に答える 6

8

オーバーライドに失敗したため

virtual double getArea() = 0;

それはと同じではありません

virtual double getArea() const = 0;

これは同じ機能ではありません。両方が単一のクラス定義内で共存できます。

于 2012-07-21T19:55:03.227 に答える
8

この場合はどうなりますか:

struct base
{
    virtual void foo(); // May implement copy-on write
    virtual void foo() const;
};

struct derived : base
{
    // Only specialize the const version, the non const
    // default suits me well. How would I specify that I don't
    // want the non-const version to be overriden ?
    void foo() const;
};
于 2012-07-21T19:58:59.493 に答える
5

これは、constメソッドのシグネチャが非メソッドとは異なるためconstです。そのため、コンパイラは実装されていないメソッドを探しています。おそらく、非constバージョンはバージョンとはまったく異なることをする可能性がありますconst。ただし、それが必要なセマンティクスである場合は、簡単に提供できます。

class Shape
{
public:
    virtual double getArea() {
        return static_cast<const Shape *>(this)->getArea();
    }
    virtual double getArea() const = 0;
};

編集では、共変リターンタイプの例を提供します。

C ++は共変の戻り型をサポートします。これは、インターフェイスがBaseArea *を返し、実装がRectangleArea *として返されることを期待している場合、契約が満たされているため、RectangleAreaがBaseAreaから派生していても問題ないためです。

メソッドまたは関数の戻り値は、そのシグネチャの一部ではありませんでした。constメソッドの性質は、戻り値とは関係ありません。これはメソッド呼び出しのオブジェクトに影響を与え、基本的に関数の最初の引数をconstオブジェクトへのポインターにします。また、関数とメソッドの引数は、そのシグネチャを制御します(名前など、名前などstatic)。

あなたはより具体的に尋ねました:

同じ行に、変異関数のみを要求するインターフェースを満たす非変異関数を提供する実装はありません。

言語がバージョンに解決できたとしても(実際には状況によっては解決できると思います) 、それは純粋な仮想として宣言されたメソッドであるconstため、非バージョンの実装を提供する必要があります。const

const非が利用可能なときにバージョンを呼び出す場合は、次のことconstを考慮してください。

class Shape {
public:
    virtual double getArea() {
        std::cout << "non-const getArea" << std::endl;
        return static_cast<const Shape *>(this)->getArea();
    }
    virtual double getArea() const = 0;
};

Rectangleあなたの例ではと相まって。それで:

Rectangle *r = new Rectangle(4,2);
Shape *s = r;
r->getArea(); // calls const version
s->getArea(); // call non-const version
于 2012-07-21T19:59:45.317 に答える
4

答えのほとんどは、規則がそのように書かれた理由を言うのではなく、言語の現在の規則の観点から許可されていない理由を述べています。ルールがあなたの提案通りにできなかった理由に答えようと思います。

StroustrupのC++の設計と進化この本は、C ++では常に許可されていなかった共変リターンを許可するために、オーバーライドルールが緩和された理由を説明しています。したがって、あなたの質問に対する1つの答えは、元々オーバーライドは署名と完全に一致する必要があり、仮想関数のコントラクトを弱めない「互換性のある」リターンタイプには例外が設けられたということです。誰もそれを考えなかったか、誰もそれを提案しなかったので、彼らはそれ以上リラックスしなかった可能性があります。D&Eは、オーバーライドルールの他の可能な緩和について言及していますが、「オーバーライドによってそのような変換を許可することの利点は、実装コストとユーザーを混乱させる可能性を上回らないと感じました」と述べています。あなたのアイデアはユーザーを混乱させる可能性が十分にあり、実際に安全上の問題を引き起こす可能性があると思うので、これは適切です。

検討:

class Square : public Rectangle
{
public:
  explicit Square(int side) : Rectangle(side, side) { }

  virtual double getArea() // N.B. non-const, overrides Shape::getArea
  {
    // class author decides this would be a sensible "sanity check"
    // (I'm not suggesting this is a good implementation)
    if (height != width)
      height = width;
    return Rectangle::getArea();
  }
};

const Square s(2);

int main()
{

  double (Rectangle::*area)() const = &Rectangle::getArea;
  double d = (s.*area)();
}

あなたのアイデアはこのコードを有効にするだろうと思います。constメンバー関数はconstオブジェクトで呼び出されますが、実際には仮想関数であるため、非constを呼び出しSquare::getArea()constオブジェクトを変更しようとします。読み取り専用メモリに格納されているため、セグメンテーション違反が発生します。

これは、例でオーバーライドするリラクゼーションを許可するとShape、未定義の動作が発生する可能性があることを示す1つの例にすぎません。より現実的なコードでは、より大きな、おそらくより微妙な問題が発生する可能性があります。

コンパイラーは非const関数のオーバーライドRectangle::getAreaを許可してはならず、したがって拒否する必要があると主張することができますSquare::getArea(「仮想関数がconstになると、元に戻すことはできません」)が、階層は非常に脆弱になります。getArea異なる定数を持つ関数を持つ中間基本クラスを追加または削除すると、Square::getArea()オーバーライドまたはオーバーロードです。仮想関数、特に共変リターンにはこのような脆弱性がすでにありますが、D&E Stroustrupによると、共変リターンは「リラクゼーションにより、キャストを使用する代わりに型システム内で重要なことを実行できる」ため、有用であると考えられています。const関数が非const関数をオーバーライドできるようにすることは、型システムにうまく適合し、重要なことを実行することを許可せず、新しい(安全な)手法を使用できるようにするためにキャストを削除しないと思います。 。

于 2012-07-21T22:00:06.103 に答える
1

メソッドをオーバーロードして、あるバージョンとconstそうでないバージョンの両方を含めることができます。(戻り型はメソッドの一意のシグニチャーの一部ではなく、メソッドは引数を受け入れない可能性がconstあるため、またはを返すものを区別する唯一の方法になります。)const X*X*

コンパイラがこれについて正確である必要がない場合、誰かが非constバージョンを基本クラスに追加する可能性があり、突然、別のメソッドをオーバーライドすることになります。

于 2012-07-21T19:57:18.710 に答える
0

constの部分が言及されているので、これをスキップします。基本クラスを確実に履行したい契約と見なすと仮定しましょう。関数のconstバージョンとnon-constバージョンは共存でき、共存する必要があるため、指定されたコントラクトを満たさない場合は通知する必要があります。

constメソッドとnonconstメソッドを持つ基本クラスを想像してみてください。たとえば、両方のフレーバーにoperator[]が含まれるコンテナーです。これで、継承しますが、両方の機能を提供しません。あなたの子供は契約を履行せず、必要な機能を提供しません。あなたの子供はポリモーフィズムを介して使用できない可能性があるため、エラーが発生するはずです

于 2012-07-21T20:02:36.200 に答える