web-dev-qa-db-ja.com

C ++で抽象クラスが必要なのはなぜですか?

OOP=クラスでポリモーフィズムについて学んだところです。抽象基本クラスがどのように役立つかを理解するのに苦労しています。

抽象クラスの目的は何ですか?実際のクラスごとに必要な関数を作成しても提供されない、抽象基本クラスを定義すると何が提供されますか?

15
Yoshua Joo Bin

抽象クラスの目的は、一連の具象サブクラスに共通のプロトコルを定義することです。これは、コードや抽象的なアイデアなどを共有するオブジェクトを定義するときに役立ちます。

抽象クラスにはインスタンスがありません。抽象クラスには、少なくとも1つの遅延メソッド(または関数)が必要です。これをC++で実現するために、純粋な仮想メンバー関数が宣言されていますが、抽象クラスでは定義されていません。

class MyClass {
    virtual void pureVirtualFunction() = 0;
}

抽象クラスをインスタンス化しようとすると、常にコンパイラエラーが発生します。

「抽象基本クラスを定義すると、実際の各クラスに必要な各関数を作成しても提供されないものは何ですか?」

ここでの主なアイデアは、コードの再利用とクラス間での適切なパーティション分割です。関数を複数のサブクラスで何度も定義するのではなく、親クラスで一度定義する方が理にかなっています。

class A {
   void func1();
   virtual void func2() = 0;
}

class B : public A {
   // inherits A's func1()
   virtual void func2();   // Function defined in implementation file
}

class C : public A {
   // inherits A's func1()
   virtual void func2();   // Function defined in implementation file
}
17
Chris Dargis

「Dog」などの抽象クラスと「bark」などの仮想メソッドを使用すると、ビーグルの樹皮がコリーとは異なる方法で実装されている場合でも、犬から継承するすべてのクラスで同じ方法で樹皮コードを呼び出すことができます。

共通の抽象親(または少なくともbark仮想メソッドを持つ共通の親)がなければ、次のことを行うのは困難です。

コリー、ビーグル、ジャーマンシェパードなどを含む犬型のベクターを用意し、それぞれを吠えさせます。コリー、ビーグル、ジャーマンシェパードを含む犬のベクターを使用すると、すべての樹皮をforループで繰り返し、それぞれの樹皮を呼び出すだけで済みます。そうでなければ、別のVector of Collies、Vector of Beaglesなどが必要になります。

質問が「なぜ具体的にできるのに犬を抽象化し、オーバーライドできるデフォルトの実装で仮想樹皮を定義するのか」である場合、答えは時々これが受け入れられるかもしれませんが、設計の観点からは、コリーでもビーグルでも他の品種でもミックスでもない犬のようなものは実際にはないので、それらはすべて犬ですが、実際には犬ではありませんが、他の派生物はありませんクラスも。また、犬の鳴き声は品種ごとに非常に多様であるため、まともな犬のグループに受け入れられるような、実際に受け入れられる吠え声のデフォルトの実装はほとんどありません。

これが目的の理解に役立つことを願っています:はい、とにかく各サブクラスに樹皮を実装する必要がありますが、共通の抽象祖先を使用すると、サブクラスを基本クラスのメンバーとして扱い、概念的に同様の動作を呼び出すことができます樹皮のようですが、実際には非常に異なる実装があります。

15
Chris

抽象クラスを使用すると、コンパイル時にプロトコルを適用できます。これらのプロトコルは、クラスファミリの一部であることの意味を定義します。

別の見方をすれば、抽象クラスは実装クラスが満たさなければならないコントラクトであるということです。それらがこの契約を満たさない場合、それらはクラスファミリの一部になることができず、契約に準拠するように変更する必要があります。提供されるコントラクトはデフォルトの機能を提供する場合がありますが、サブクラスに任せて、より具体的または異なる機能を定義する一方で、コントラクトのスコープ内にとどまります。

小規模なプロジェクトの場合、これは役に立たないように見えるかもしれませんが、抽象クラスコントラクトを通じてドキュメントを提供するので、大規模なプロジェクトの場合、適合性と構造を提供します。これにより、コードの保守性が向上し、サブクラスがそれぞれ同じプロトコルを持つようになり、新しいサブクラスの使用と開発が容易になります。

7
sean

私は犬を飼っている。メソッドの樹皮を持つ抽象クラスの犬。私の特定の犬は一匹の樹皮を作ります。他の犬は別の方法で吠えます。したがって、犬を抽象的な方法で定義すると便利です。

2
Ed Heal

抽象クラスの目的は、他のクラスが継承できる適切な基本クラスを提供することです。抽象クラスは、オブジェクトのインスタンス化には使用できず、インターフェースとしてのみ機能します。抽象クラスのオブジェクトをインスタンス化しようとすると、コンパイルエラーが発生します。 (vtableエントリが、抽象クラスで言及した仮想関数のメモリ位置で満たされていないため)

したがって、ABCのサブクラスをインスタンス化する必要がある場合は、各仮想関数を実装する必要があります。つまり、ABCによって宣言されたインターフェースをサポートします。派生クラスで純粋な仮想関数をオーバーライドせずに、そのクラスのオブジェクトをインスタンス化しようとすると、コンパイルエラーになります。

例:

class mobileinternet
{
public:
virtual enableinternet()=0;//defines as virtual so that each class can overwrite
};


class 2gplan : public mobileinternet

{
    private:

         int providelowspeedinternet(); //logic to give less speed.

    public:

         void enableinternet(int) {
                                     // implement logic
                                 }

};

//similarly

class 3gplan : public enableinternet
{
   private: high speed logic (different then both of the above)

   public: 
          /*    */
}

この例では、理解できます。

文字列を表示する方法が2つあるとします。

DisplayDialog(string s);
PrintToConsole(string s);

そして、あなたはこれらの2つの方法の間で切り替えることができるいくつかのコードを書きたいと思います:

void foo(bool useDialogs) {
    if (useDialogs) {
        DisplayDialog("Hello, World!");
    } else {
        PrintToConsole("Hello, World!");
    }

    if (useDialogs) {
        DisplayDialog("The result of 2 * 3 is ");
    } else {
        PrintToConsole("The result of 2 * 3 is ");
    }

    int i = 2 * 3;
    string s = to_string(i);

    if (useDialogs) {
        DisplayDialog(s);
    } else {
        PrintToConsole(s);
    }        
}

このコードは、文字列の表示に使用される特定のメソッドと密接に結び付いています。メソッドを追加したり、メソッドの選択方法を変更したりすると、これを使用するすべてのコードに影響します。このコードは、文字列を表示するために使用する一連のメソッドに密結合です。

抽象基本クラスは、その機能を実装するコードの一部の機能を使用するdecouplingコードの方法です。これを行うには、タスクを実行するさまざまな方法すべてに共通のインターフェースを定義します。

class AbstractStringDisplayer {
public:
    virtual display(string s) = 0;

    virtual ~AbstractStringDisplayer();
};

void foo(AbstractStringDisplayer *asd) {
    asd->display("Hello, World!");
    asd->display("The result of 2 * 3 is ");

    int i = 2 * 3;
    string s = to_string(i);

    asd->display(s);
}

int main() {
    AbstractStringDisplayer *asd = getStringDisplayerBasedOnUserPreferencesOrWhatever();

    foo(asd);
}

AbstractStringDisplayerで定義されたインターフェイスを使用して、文字列を表示する新しい方法をいくつでも作成および使用でき、抽象インターフェイスを使用するコードを変更する必要はありません。

0
bames53
abstract class dog
{
bark();
}

// function inside another module

dogbarking(dog obj)
{
   dog.bark(); // function will call depend up on address inside the obj
}


// our class
ourclass: inherit dog
{
    bark()
    {
         //body
     }
}


main()
{
    ourclass obj;
    dogbarking(obj);
}

dogbarkingは別のモジュールで記述された関数であることがわかります。それは抽象クラスの犬だけを知っています。クラス内で関数barkを呼び出すことができますが。メイン関数でクラスのオブジェクトを作成し、抽象クラスdogの参照オブジェクトを使用して受け取った場所dogbarkingに渡します。

0
Rajesh

抽象クラスは、実装するインターフェースを定義するために使用されます。いくつかの参考文献をご覧ください:

http://en.wikibooks.org/wiki/C%2B%2B_Programming/Classes/Abstract_Classes

0
OldProgrammer

AbstractClassから派生する型を持つすべてのオブジェクトに必要な機能があるが、AbstractClass自体に適切に実装できない場合は、基本クラスとしての抽象クラスAbstractClassが必要です。

古い、やや人工的なOO派生クラスVehicleCar、...を持つ基本クラスMotorcycleの例は、ここに良い例を示します。メソッドmove() -CarまたはMotorcycleの移動方法を実装できますが、Vehiclesは一般的な方法で移動しないため、Vehicle::move()は純粋な仮想である必要があり、Vehicleは抽象的です。

0
us2012

各クラスに必要な関数をそれぞれ作成しないのはなぜですか? (C++)

派生クラスごとに、abstractとマークされた必要な各関数を作成する必要があります。

疑問がある場合、なぜ抽象クラスで抽象関数を作成するのですか?

厳密な ランタイムポリモーフィズム を許可します。

インターフェースvs抽象クラス(一般OO) もお読みください

0
Tilak