[収集]c++抽象クラス、純虚関数、および純粋虚構造関数を巧みに用いてインタフェースクラスを実現
Java、C#には抽象関数、抽象クラスを示すキーワードabstractがありますが、C++にはこのキーワードはありません.明らかに、C++でも基底クラスで関数を宣言するだけで、具体的な実装を書く必要はありません.そのC++ではどのようにこの機能を実現するか、答えは純虚関数です.純粋な虚関数を含むクラスは抽象クラスであり,オブジェクトを生成することはできず,派生するしかない.彼が派生したクラスの純虚関数は書き換えられず、その派生クラスは抽象クラスである.純粋な虚関数を定義するのは,インスタンス化のような抽象データ構造自体に意味がないか,実装を与えても意味がないため,ベースクラスをインスタンス化できないようにするためである.
一.じゅんかそうかんすう
多くの場合、ベースクラスでは意味のある虚関数定義が与えられない場合があります.この場合、それを純虚関数として説明し、その定義を派生クラスに残しておくことができます.純虚関数を定義する一般的な形式は、次のとおりです.
クラス名{
virtual戻り値タイプ関数名(パラメータテーブル)=0;//後の「=0」が必要です.そうしないと、虚関数になります
};
純虚関数は、ベースクラスで説明される虚関数であり、ベースクラスでは定義されず、派生クラスに独自のバージョンを定義する必要があります.純粋な虚関数は、各派生クラスに共通のインタフェースを提供します.
ベースクラスから継承された純粋な虚関数は,派生クラスでは虚関数である.
二.抽象クラス
1.クラスに少なくとも1つの純粋な虚関数がある場合、このクラスは抽象クラス(abstract class)と呼ばれる.
抽象クラスには、純粋な虚関数だけでなく、虚関数も含まれます.抽象クラスの純粋な虚関数は、抽象クラスで定義されているか、抽象ベースクラスから継承され、再定義されている可能性があります.
2.抽象クラスの特徴、すなわち抽象クラスは、オブジェクトインスタンスを直接作成するのではなく、他のクラスを派生するベースクラスとして使用する必要があります.
抽象クラスはオブジェクトを作成するために使用できません.派生クラスにインタフェース仕様を提供するためにのみ使用できます.派生クラスでは、ベースクラスの純粋な虚関数を再ロードする必要があります.そうしないと、抽象クラスと見なされます.
3.effective c++では、純虚関数を実装(定義)することができる(純虚関数である以上、なぜ実装できるのか.これにはどのようなメリットがあるのか.以下、「純虚解析関数を巧みに用いてインタフェースクラスを実装する」では、この機能の目的を説明する.)が、オブジェクトインスタンスを作成することはできず、抽象クラスの概念も体現している.
三.かそうこうぞうかんすう
虚解析関数:この解析関数を虚解析関数と呼ぶキーワードvirtualを解析関数の前に付けて説明します.コンストラクション関数は虚関数として宣言できませんが、コンストラクション関数は虚関数として宣言できます.
一般的に、クラスに虚関数が定義されている場合は、構造関数も虚構造関数として定義する必要があります.
例:
class B
{
virtual ~B();//ダミー構造関数
…
};
次の例を示します.
四.純粋な虚析関数を巧みに用いてインタフェースクラスを実現する
c++はjavaのように純粋なインタフェースクラスの構文ではありませんが、いくつかの手段で同じ機能を実現することができます.(1)「protected」でインタフェースクラスを実現できますか?
次のコードを参照してください.
クラスでクラスのコンストラクション関数またはコンストラクション関数をprotectedと明示することで、クラスがインスタンス化されることを効果的に防止できます.実用的に言えば、コンストラクション関数はprotectedの方が役に立ち、クラスがインスタンス化されないことを保証できるに違いありません.コンストラクション関数がprotectedであれば、コンストラクション関数がprotectedでなければ、コンパイルに合格する脆弱性もあります.以下のようにします.
Case1:
Case2:
Case3:
だから、実行可能な方法はこうです.
B publicはAから継承されるため、Aの構造または構造関数に完全にアクセスできますが、次のようになります.
このようにBがAを再ロードした構造関数を表示しても:
まとめ:
protectedのような方法は適切ではないようで、一定のルールを守っている場合に実用的な価値があるのは確かだが、あまり通用しない.
(2)インタフェースクラスをどのように実現すべきか.
実は上のprotectedの考え方は正しいので、親をインスタンス化できないにほかならないが、親をインスタンス化できないためには、純粋な虚関数を使う方法もある.
このように書くのは悪くないようで、以前はみんなクラスの中の一般的なメンバー関数を純虚と書いていましたが、今回は構造関数を純虚と書いて、もっと汎用性を高めて、コンパイルも通過しましたが、リンクの時に問題が発生して、Aの構造関数の実現が見つからないと間違えて報告しました.明らかに、Aの構造は純虚だからです.
では、上記のコードをどのように修正すれば、上記errorを除去したり、ベースクラスをインスタンス化したりすることができますか?次のようになります.
このようにしてついに大きな成果が得られ,構造関数は虚関数として許されないため,上の構造関数の使い方に構造関数を置き換えることはできないことに注意する.
以上の文法は本当にこのような状況のために存在しているだけで、一般的に私たちが虚類の中で明らかにしたインタフェースのためです.
virtual foo()= 0;
virtual foo()= 0 {}
この2つの書き方はまったく区別がなく、純虚関数のデフォルト実装は、それが構造関数であるだけで意味がある!!!
だから、外国人は完全にこの目的のためにこの文法を発明したと言える.
最終インタフェースクラス
完璧なはずだよね[備考:内容はネットに集められることが多い~]
一.じゅんかそうかんすう
多くの場合、ベースクラスでは意味のある虚関数定義が与えられない場合があります.この場合、それを純虚関数として説明し、その定義を派生クラスに残しておくことができます.純虚関数を定義する一般的な形式は、次のとおりです.
クラス名{
virtual戻り値タイプ関数名(パラメータテーブル)=0;//後の「=0」が必要です.そうしないと、虚関数になります
};
純虚関数は、ベースクラスで説明される虚関数であり、ベースクラスでは定義されず、派生クラスに独自のバージョンを定義する必要があります.純粋な虚関数は、各派生クラスに共通のインタフェースを提供します.
ベースクラスから継承された純粋な虚関数は,派生クラスでは虚関数である.
二.抽象クラス
1.クラスに少なくとも1つの純粋な虚関数がある場合、このクラスは抽象クラス(abstract class)と呼ばれる.
抽象クラスには、純粋な虚関数だけでなく、虚関数も含まれます.抽象クラスの純粋な虚関数は、抽象クラスで定義されているか、抽象ベースクラスから継承され、再定義されている可能性があります.
2.抽象クラスの特徴、すなわち抽象クラスは、オブジェクトインスタンスを直接作成するのではなく、他のクラスを派生するベースクラスとして使用する必要があります.
抽象クラスはオブジェクトを作成するために使用できません.派生クラスにインタフェース仕様を提供するためにのみ使用できます.派生クラスでは、ベースクラスの純粋な虚関数を再ロードする必要があります.そうしないと、抽象クラスと見なされます.
3.effective c++では、純虚関数を実装(定義)することができる(純虚関数である以上、なぜ実装できるのか.これにはどのようなメリットがあるのか.以下、「純虚解析関数を巧みに用いてインタフェースクラスを実装する」では、この機能の目的を説明する.)が、オブジェクトインスタンスを作成することはできず、抽象クラスの概念も体現している.
三.かそうこうぞうかんすう
虚解析関数:この解析関数を虚解析関数と呼ぶキーワードvirtualを解析関数の前に付けて説明します.コンストラクション関数は虚関数として宣言できませんが、コンストラクション関数は虚関数として宣言できます.
一般的に、クラスに虚関数が定義されている場合は、構造関数も虚構造関数として定義する必要があります.
例:
class B
{
virtual ~B();//ダミー構造関数
…
};
次の例を示します.
#include <stdio.h>
class Animal
{
public:
Animal() //
{
printf(" Animal construct!
");
}
virtual void shout() = 0;
virtual void impl() = 0;
virtual ~Animal() {printf(" Animal destory!
");}; //
};
void Animal::impl() // 。
{
printf(" Animal: I can be implement!
");
}
class Dog: public Animal
{
public:
Dog()
{
printf(" Dog construct!
");
}
virtual void shout() // ,
{
printf(" Dog: wang! wang! wang!
");
}
virtual void impl()
{
printf(" Dog: implement of Dog!
");
}
virtual ~Dog() {printf(" Dog destory!
");}; //
};
class Cat: public Animal
{
public:
Cat()
{
printf(" Cat construct!
");
}
virtual void shout() // ,
{
printf(" Cat: miao! miao! miao!
");
}
virtual void impl()
{
printf(" Cat: implement of Cat!
");
}
virtual ~Cat() {printf(" Cat destory!
");}; //
};
/*
Animal f() // error,
{
}
void display( Animal a) //error,
{
}
*/
//ok,
Animal &display(Animal &a)
{
Dog d;
Animal &p = d;
return p;
}
void test_func()
{
//Animal a; // error:
Dog dog; //ok,
Cat cat; //ok,
printf("
");
Animal *animal = &dog;
animal->shout();
animal->impl();
printf("
");
animal = &cat;
animal->shout();
animal->impl();
printf("
");
}
int main()
{
test_func();
while(1);
}
//result:
/*
Animal construct!
Dog construct!
Animal construct!
Cat construct!
Dog: wang! wang! wang!
Dog: implement of Dog!
Cat: miao! miao! miao!
Cat: implement of Cat!
Cat destory!
Animal destory!
Dog destory!
Animal destory!
*/
(YC: )
四.純粋な虚析関数を巧みに用いてインタフェースクラスを実現する
c++はjavaのように純粋なインタフェースクラスの構文ではありませんが、いくつかの手段で同じ機能を実現することができます.(1)「protected」でインタフェースクラスを実現できますか?
次のコードを参照してください.
#include <stdio.h>
class A
{
protected:
virtual ~A()
{
printf(" A:
");
}
};
class B : public A
{
public:
virtual ~B()
{
printf(" B:
");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
//A* p1 = new A; //error:[1]
//delete p1;
B* p2 = new B; //ok:[2] , :
delete p2; /* B:
A: */( A , )
//A* p3 = new B;
//delete p3; //error:[3]
return 0;
}
クラスでクラスのコンストラクション関数またはコンストラクション関数をprotectedと明示することで、クラスがインスタンス化されることを効果的に防止できます.実用的に言えば、コンストラクション関数はprotectedの方が役に立ち、クラスがインスタンス化されないことを保証できるに違いありません.コンストラクション関数がprotectedであれば、コンストラクション関数がprotectedでなければ、コンパイルに合格する脆弱性もあります.以下のようにします.
Case1:
class A
{
protected:
A()
{
printf(" A: A()
");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
A* p1 = new A; // , protected
delete p1;
return 0;
}
Case2:
class A
{
protected:
~A()
{
printf(" A: ~A()
");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
A* p1 = new A; // , A ,
return 0;
}
( : main :
int _tmain(int argc, _TCHAR* argv[])
{
A a;
return 0;
}
, protected A::~A(). ?
)
Case3:
class A
{
protected:
~A()
{
printf(" A: ~A()
");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
A* p1 = new A;
delete p1; // , A protected
return 0;
}
だから、実行可能な方法はこうです.
class A
{
protected:
virtual ~A()
{
printf(" A: ~A()
");
}
};
class B : public A
{
};
int _tmain(int argc, _TCHAR* argv[])
{
B* p =new B; //ok: (YC: “(1) ” ok )
delete p;
return 0;
}
B publicはAから継承されるため、Aの構造または構造関数に完全にアクセスできますが、次のようになります.
int _tmain(int argc, _TCHAR* argv[])
{
A* p =new B;
delete p; //error: p A , A , A protected
return 0;
}
このようにBがAを再ロードした構造関数を表示しても:
class A
{
protected:
virtual ~A()
{
printf(" A: ~A()
");
}
};
class B : public A
{
public:
virtual ~B()
{
printf(" B: ~B()
");
}
};
int _tmain(int argc, _TCHAR* argv[])
{
A* p =new B;
delete p; //error: , , A ,
return 0
}
まとめ:
protectedのような方法は適切ではないようで、一定のルールを守っている場合に実用的な価値があるのは確かだが、あまり通用しない.
(2)インタフェースクラスをどのように実現すべきか.
実は上のprotectedの考え方は正しいので、親をインスタンス化できないにほかならないが、親をインスタンス化できないためには、純粋な虚関数を使う方法もある.
class A
{
public: // protected
virtual ~A() = 0;
};
class B : public A
{
};
int _tmain(int argc, _TCHAR* argv[])
{
B* p =new B;
delete p; // ok, error
return 0;
}
このように書くのは悪くないようで、以前はみんなクラスの中の一般的なメンバー関数を純虚と書いていましたが、今回は構造関数を純虚と書いて、もっと汎用性を高めて、コンパイルも通過しましたが、リンクの時に問題が発生して、Aの構造関数の実現が見つからないと間違えて報告しました.明らかに、Aの構造は純虚だからです.
では、上記のコードをどのように修正すれば、上記errorを除去したり、ベースクラスをインスタンス化したりすることができますか?次のようになります.
class A
{
public: // protected
virtual ~A() = 0 // ,
{ // ( )
printf(" A: ~A()
");
}
};
class B : public A
{
};
int _tmain(int argc, _TCHAR* argv[])
{
B* p =new B;
delete p;
A* p2 =new B;
delete p2; // , A public
return 0;
}
//result:
/*
A: ~A()
A: ~A()
*/
このようにしてついに大きな成果が得られ,構造関数は虚関数として許されないため,上の構造関数の使い方に構造関数を置き換えることはできないことに注意する.
以上の文法は本当にこのような状況のために存在しているだけで、一般的に私たちが虚類の中で明らかにしたインタフェースのためです.
virtual foo()= 0;
virtual foo()= 0 {}
この2つの書き方はまったく区別がなく、純虚関数のデフォルト実装は、それが構造関数であるだけで意味がある!!!
だから、外国人は完全にこの目的のためにこの文法を発明したと言える.
最終インタフェースクラス
classInterface
{
public:
virtual ~Interface() = 0 {}
};
完璧なはずだよね[備考:内容はネットに集められることが多い~]