インターフェイスを表すクラスをセットアップするにはどうすればよいですか? これは単なる抽象基本クラスですか?
17 に答える
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 に割り当てられたクラス メソッドです。
class IDemo
{
public:
virtual ~IDemo() {}
virtual void OverrideMe() = 0;
};
class Child : public IDemo
{
public:
virtual void OverrideMe()
{
// do stuff
}
};
C#/Javaで抽象基本クラスに加えて特別な Interface 型カテゴリを使用する理由は、C#/ Javaが多重継承をサポートしていないためです。
C++ は多重継承をサポートしているため、特別な型は必要ありません。非抽象 (純粋仮想) メソッドを持たない抽象基本クラスは、C#/Java インターフェイスと機能的に同等です。
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 つの重要なことがあると思います。
インターフェイスで仮想デストラクタを宣言するか、保護された非仮想デストラクタを作成して、誰かが type のオブジェクトを削除しようとした場合に未定義の動作を回避します
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 } }
上記のすべての良い答え。覚えておくべきもう 1 つの点は、純粋な仮想デストラクタを使用することもできるということです。唯一の違いは、まだ実装する必要があることです。
混乱している?
--- header file ----
class foo {
public:
foo() {;}
virtual ~foo() = 0;
virtual bool overrideMe() {return false;}
};
---- source ----
foo::~foo()
{
}
これを行う主な理由は、私が持っているようにインターフェースメソッドを提供したいが、それらのオーバーライドをオプションにしたい場合です。
クラスをインターフェイス クラスにするには、純粋仮想メソッドが必要ですが、すべての仮想メソッドには既定の実装があるため、純粋仮想にするために残された唯一のメソッドはデストラクタです。
派生クラスでデストラクタを再実装することは、まったく大したことではありません。派生クラスでは、仮想であろうとなかろうと、常にデストラクタを再実装します。
NVI(非仮想インターフェースパターン)で実装されたコントラクトクラスを検討することもできます。例えば:
struct Contract1 : boost::noncopyable
{
virtual ~Contract1() = default;
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:
void do_f(Parameters p) override; // From contract 1.
void do_g(Parameters p) override; // From contract 2.
};
Microsoft の C++ コンパイラを使用している場合は、次のようにすることができます。
struct __declspec(novtable) IFoo
{
virtual void Bar() = 0;
};
class Child : public IFoo
{
public:
virtual void Bar() override { /* Do Something */ }
}
私がこのアプローチを気に入っているのは、インターフェイス コードが大幅に小さくなり、生成されるコード サイズが大幅に小さくなるためです。novtable を使用すると、そのクラスの vtable ポインターへのすべての参照が削除されるため、直接インスタンス化することはできません。こちらのドキュメントを参照してください - novtable。
そこに書かれていることへの少しの追加:
まず、デストラクタも純粋な仮想であることを確認してください
第 2 に、適切な手段として、実装するときに (通常ではなく) 仮想的に継承することをお勧めします。
インターフェイスを定義するための事実上の標準であること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 と状態の変化の数に応じて、これはes またはsよりも高速になる可能性があります(ターンアラウンドは約 3 ~ 4秒と予想されますが、常に最初に測定します。switch()
if
if
std::function<>
関数ポインタを選択すると、すべてのオブジェクト データを 内で管理できる場合がIBase
あります。この時点から、値の回路図を作成できますIBase
(たとえば、std::vector<IBase>
動作します)。これは、コンパイラと STL コードによっては遅くなる可能性があることに注意してください。また、現在の の実装はstd::function<>
、関数ポインタや仮想関数と比較するとオーバーヘッドが発生する傾向があります (これは将来変更される可能性があります)。
私はまだ C++ 開発の初心者です。Visual Studio (VS) から始めました。
しかし、__interface
VS (.NET)については誰も言及していないようです。これがインターフェイスを宣言する良い方法かどうかはよくわかりません。しかし、それは追加の強制を提供しているようです(ドキュメントに記載されています)。virtual TYPE Method() = 0;
は自動的に変換されるため、明示的に指定する必要はありません。
__interface IMyInterface {
HRESULT CommitX();
HRESULT get_X(BSTR* pbstrName);
};
ただし、.NET でしか使用できないため、クロス プラットフォームのコンパイルの互換性が懸念されるため、使用しません。
誰かがそれについて何か面白いことがあれば、共有してください。:-)
ありがとう。
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 つのクラスが同じ関数を実装する方法を見てきましたが、形状に固有の面積を計算するために異なるアルゴリズムを使用しています。