インターフェイスを表すクラスを設定するにはどうすればよいですか?これは単なる抽象基本クラスですか?
インターフェイスを表すクラスを設定するにはどうすればよいですか?これは単なる抽象基本クラスですか?
回答:
bradtgmurrayによる回答を拡張するには、仮想デストラクタを追加して、インターフェースの純粋仮想メソッドリストに1つの例外を作成することができます。これにより、具体的な派生クラスを公開することなく、ポインターの所有権を別のパーティに渡すことができます。インターフェイスには具体的なメンバーがないため、デストラクタは何もする必要がありません。関数を仮想とインラインの両方として定義することは矛盾するように思えるかもしれませんが、私を信じてください-そうではありません。
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Parent
{
public:
virtual ~Parent();
};
class Child : public Parent, public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};
仮想デストラクタの本体を含める必要はありません-一部のコンパイラは空のデストラクタの最適化に問題があり、デフォルトを使用する方がよいことがわかりました。
=0
、ボディを持つ純粋な仮想()デストラクタを定義することです。ここでの利点は、理論的には、vtableに有効なメンバーがないことをコンパイラーが理論的に確認し、それを完全に破棄できることです。ボディを備えた仮想デストラクタを使用すると、たとえば、デストラクタを(仮想的に)this
ポインタを介して構築の途中で(構築されたオブジェクトがまだParent
タイプである場合)呼び出すことができるため、コンパイラは有効なvtableを提供する必要があります。したがって、this
構築中に仮想デストラクタを明示的に呼び出さない場合:)コードサイズを節約できます。
override
キーワードを指定してコンパイル時の引数と戻り値の型のチェックを許可できることを忘れないでください。たとえば、児童の宣言ではvirtual void OverrideMe() override;
純粋な仮想メソッドでクラスを作成します。これらの仮想メソッドをオーバーライドする別のクラスを作成して、インターフェースを使用します。
純粋仮想メソッドは、仮想として定義され、0に割り当てられたクラスメソッドです。
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Child : public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
};
override
C ++ 11
C#/ Javaで抽象基本クラスに加えて特別なインターフェイスタイプカテゴリがある理由は、C#/ Javaが多重継承をサポートしていないためです。
C ++は多重継承をサポートしているため、特別な型は必要ありません。非抽象(純粋仮想)メソッドのない抽象基本クラスは、C#/ Javaインターフェースと機能的に同等です。
Thread
インスタンスにしたかったことです。多重継承は、設計だけでなく構成も悪い場合があります。それはすべてケースに依存します。
C ++には「インターフェース」自体の概念はありません。私の知る限り、インターフェースは多重継承の欠如を回避するためにJavaで最初に導入されました。この概念は非常に有用であることが判明し、C ++でも抽象基本クラスを使用して同じ効果を得ることができます。
抽象基本クラスは、少なくとも1つのメンバー関数(Javaの用語ではメソッド)が、次の構文を使用して宣言された純粋な仮想関数であるクラスです。
class A
{
virtual void foo() = 0;
};
抽象基本クラスはインスタンス化できません。つまり、クラスAのオブジェクトを宣言することはできません。Aからクラスを派生させることしかできませんが、の実装を提供しない派生クラスfoo()
も抽象化されます。抽象化をやめるために、派生クラスはそれが継承するすべての純粋仮想関数の実装を提供する必要があります。
抽象基本クラスは、純粋な仮想ではないデータメンバーとメンバー関数を含むことができるため、インターフェース以上のものになる可能性があることに注意してください。インターフェースに相当するものは、純粋な仮想関数のみを持つデータのない抽象基本クラスです。
また、Mark Ransomが指摘したように、抽象基本クラスは、基本クラスと同様に、仮想デストラクタを提供する必要があります。
テストできる限り、仮想デストラクタを追加することは非常に重要です。で作成new
および破棄されたオブジェクトを使用していますdelete
。
インターフェイスに仮想デストラクタを追加しない場合、継承されたクラスのデストラクタは呼び出されません。
class IBase {
public:
virtual ~IBase() {}; // destructor, use it to call destructor of the inherit classes
virtual void Describe() = 0; // pure virtual method
};
class Tester : public IBase {
public:
Tester(std::string name);
virtual ~Tester();
virtual void Describe();
private:
std::string privatename;
};
Tester::Tester(std::string name) {
std::cout << "Tester constructor" << std::endl;
this->privatename = name;
}
Tester::~Tester() {
std::cout << "Tester destructor" << std::endl;
}
void Tester::Describe() {
std::cout << "I'm Tester [" << this->privatename << "]" << std::endl;
}
void descriptor(IBase * obj) {
obj->Describe();
}
int main(int argc, char** argv) {
std::cout << std::endl << "Tester Testing..." << std::endl;
Tester * obj1 = new Tester("Declared with Tester");
descriptor(obj1);
delete obj1;
std::cout << std::endl << "IBase Testing..." << std::endl;
IBase * obj2 = new Tester("Declared with IBase");
descriptor(obj2);
delete obj2;
// this is a bad usage of the object since it is created with "new" but there are no "delete"
std::cout << std::endl << "Tester not defined..." << std::endl;
descriptor(new Tester("Not defined"));
return 0;
}
前のコードをなしvirtual ~IBase() {};
で実行すると、デストラクタTester::~Tester()
が呼び出されないことがわかります。
私の答えは基本的に他の答えと同じですが、他にも重要なことが2つあると思います。
インターフェイスで仮想デストラクタを宣言するか、保護された非仮想デストラクタを作成して、誰かがタイプのオブジェクトを削除しようとした場合の未定義の動作を回避しますIDemo
。
仮想継承を使用して、多重継承による問題を回避します。(インターフェースを使用すると、多くの場合、複数の継承があります。)
そして他の答えのように:
これらの仮想メソッドをオーバーライドする別のクラスを作成して、インターフェースを使用します。
class IDemo
{
public:
virtual void OverrideMe() = 0;
virtual ~IDemo() {}
}
または
class IDemo
{
public:
virtual void OverrideMe() = 0;
protected:
~IDemo() {}
}
そして
class Child : virtual public IDemo
{
public:
virtual void OverrideMe()
{
//do stuff
}
}
C ++ 11では、継承を完全に回避できます。
struct Interface {
explicit Interface(SomeType& other)
: foo([=](){ return other.my_foo(); }),
bar([=](){ return other.my_bar(); }), /*...*/ {}
explicit Interface(SomeOtherType& other)
: foo([=](){ return other.some_foo(); }),
bar([=](){ return other.some_bar(); }), /*...*/ {}
// you can add more types here...
// or use a generic constructor:
template<class T>
explicit Interface(T& other)
: foo([=](){ return other.foo(); }),
bar([=](){ return other.bar(); }), /*...*/ {}
const std::function<void(std::string)> foo;
const std::function<void(std::string)> bar;
// ...
};
この場合、インターフェースには参照セマンティクスがあります。つまり、オブジェクトがインターフェースよりも長く存続することを確認する必要があります(値セマンティクスでインターフェースを作成することも可能です)。
これらのタイプのインターフェースには長所と短所があります。
最後に、継承は複雑なソフトウェア設計におけるすべての悪の根です。でショーン親の値セマンティクスと多型を概念ベース(強く推奨、この技術のより良いバージョンが説明されている)以下の場合を検討します。
MyShape
インターフェイスを使用して多形的に形状を処理するアプリケーションがあるとします。
struct MyShape { virtual void my_draw() = 0; };
struct Circle : MyShape { void my_draw() { /* ... */ } };
// more shapes: e.g. triangle
アプリケーションでは、YourShape
インターフェースを使用して異なる形状で同じことを行います。
struct YourShape { virtual void your_draw() = 0; };
struct Square : YourShape { void your_draw() { /* ... */ } };
/// some more shapes here...
ここで、アプリケーションで開発したいくつかの形状を使用したいとします。概念的には、形状は同じインターフェースですが、アプリケーションで形状を機能させるには、形状を次のように拡張する必要があります。
struct Circle : MyShape, YourShape {
void my_draw() { /*stays the same*/ };
void your_draw() { my_draw(); }
};
まず、私の形状を変更することはまったく不可能かもしれません。さらに、多重継承はスパゲッティコードへの道を導きます(TheirShape
インターフェイスを使用する3番目のプロジェクトが登場すると想像してください...描画関数も呼び出すとどうなりますmy_draw
か?)。
更新:非継承ベースのポリモーフィズムに関する新しい参照がいくつかあります。
Circle
クラスは貧弱なデザインです。このAdapter
ような場合はパターンを使用する必要があります。少し厳しいように聞こえる場合は申し訳ありませんが、Qt
継承について判断する前に、実際のライブラリを使用してみてください。継承は人生をずっと簡単にします。
Adapter
パターンを使用してCircleを修正する例(おそらくideone)を教えていただけますか?その利点を知りたいです。
Square
はまだそこにいないのか知ることができますか?予知?それが現実から切り離されている理由です。そして実際には、「MyShape」ライブラリに依存することを選択した場合、最初からそのインターフェースに採用できます。形状の例では多くのナンセンスがあります(そのうちの1つは2つのCircle
構造体があることです)が、アダプターは次のようになります-> ideone.com/UogjWk
上記のすべての良い答え。覚えておくべきもう1つのこと-純粋な仮想デストラクタを持つこともできます。唯一の違いは、まだ実装する必要があることです。
混乱していますか?
--- header file ----
class foo {
public:
foo() {;}
virtual ~foo() = 0;
virtual bool overrideMe() {return false;}
};
---- source ----
foo::~foo()
{
}
これを行いたい主な理由は、私が持っているように、インターフェイスメソッドを提供したいが、それらのオーバーライドをオプションにしたい場合です。
クラスをインターフェースクラスにするには純粋仮想メソッドが必要ですが、すべての仮想メソッドにはデフォルトの実装があるため、純粋仮想にするために残された唯一のメソッドはデストラクタです。
派生クラスでデストラクタを再実装することは大したことではありません-私は常に、派生クラスでデストラクタを再実装します。
MicrosoftのC ++コンパイラを使用している場合は、次のことを実行できます。
struct __declspec(novtable) IFoo
{
virtual void Bar() = 0;
};
class Child : public IFoo
{
public:
virtual void Bar() override { /* Do Something */ }
}
この方法が気に入ったのは、インターフェースコードが大幅に小さくなり、生成されるコードのサイズが大幅に小さくなるためです。novtableを使用すると、そのクラスのvtableポインターへのすべての参照が削除されるため、直接インスタンス化することはできません。こちらのドキュメントをご覧ください-novtable。
novtable
標準よりもvirtual void Bar() = 0;
= 0;
ました)。理解できない場合は、ドキュメントを読んでください。
= 0;
それがまったく同じことをする非標準的な方法だと思った。
そこに書かれていることへの少しの追加:
まず、デストラクタも純粋に仮想であることを確認してください
第2に、適切な対策のために、実装するときは(通常ではなく)仮想的に継承したい場合があります。
NVI(非仮想インターフェイスパターン)で実装されたコントラクトクラスを検討することもできます。例えば:
struct Contract1 : boost::noncopyable
{
virtual ~Contract1();
void f(Parameters p) {
assert(checkFPreconditions(p)&&"Contract1::f, pre-condition failure");
// + class invariants.
do_f(p);
// Check post-conditions + class invariants.
}
private:
virtual void do_f(Parameters p) = 0;
};
...
class Concrete : public Contract1, public Contract2
{
private:
virtual void do_f(Parameters p); // From contract 1.
virtual void do_g(Parameters p); // From contract 2.
};
私はまだC ++開発の新人です。私はVisual Studio(VS)から始めました。
しかし、__interface
VS (.NET)については誰も言及していないようです。これがインターフェースを宣言する良い方法であるかどうかは、私にはよくわかりません。しかし、それは追加の執行を提供するようです(ドキュメントで言及されています)。virtual TYPE Method() = 0;
自動的に変換されるため、明示的にを指定する必要はありません。
__interface IMyInterface {
HRESULT CommitX();
HRESULT get_X(BSTR* pbstrName);
};
ただし、.NETでのみ使用できるため、クロスプラットフォームコンパイルの互換性について懸念があるため、使用しません。
何か面白いことがあったらシェアしてください。:-)
ありがとう。
それがvirtual
インターフェースを定義する事実上の標準であるのは事実ですが、C ++のコンストラクターに付属している古典的なCのようなパターンを忘れないでください。
struct IButton
{
void (*click)(); // might be std::function(void()) if you prefer
IButton( void (*click_)() )
: click(click_)
{
}
};
// call as:
// (button.*click)();
これには、クラスを再構築せずにイベントランタイムを再バインドできるという利点があります(C ++には多相型を変更するための構文がないため、これはカメレオンクラスの回避策です)。
チップ:
click
れます)、子孫のコンストラクターに入力します。protected
メンバーとして関数ポインターがあり、public
参照やゲッターがある場合があります。if
コード内のsと状態の変化の数によっては、これはswitch()
esまたはif
s よりも速い場合があります(所要時間は約3〜4 if
秒と予想されますが、常に最初に測定します。std::function<>
関数ポインタを選択すると、内のすべてのオブジェクトデータを管理できる場合がありますIBase
。この時点から、値の回路図を作成できますIBase
(たとえば、std::vector<IBase>
機能します)。コンパイラとSTLコードによっては、これが遅くなる可能性があることに注意してください。また、現在のの実装でstd::function<>
は、関数ポインターや仮想関数(これは将来変更される可能性があります)と比較すると、オーバーヘッドが発生する傾向があります。class Shape
{
public:
// pure virtual function providing interface framework.
virtual int getArea() = 0;
void setWidth(int w)
{
width = w;
}
void setHeight(int h)
{
height = h;
}
protected:
int width;
int height;
};
class Rectangle: public Shape
{
public:
int getArea()
{
return (width * height);
}
};
class Triangle: public Shape
{
public:
int getArea()
{
return (width * height)/2;
}
};
int main(void)
{
Rectangle Rect;
Triangle Tri;
Rect.setWidth(5);
Rect.setHeight(7);
cout << "Rectangle area: " << Rect.getArea() << endl;
Tri.setWidth(5);
Tri.setHeight(7);
cout << "Triangle area: " << Tri.getArea() << endl;
return 0;
}
結果:長方形領域:35三角形領域:17
抽象クラスがどのようにgetArea()の観点からインターフェースを定義し、他の2つのクラスが同じ関数を実装したが、形状に固有の領域を計算するアルゴリズムが異なるのを見てきました。