注:以下はC ++ 03コードですが、今後2年以内にC ++ 11への移行を予定しているため、この点に留意する必要があります。
私は、C ++で抽象的なインターフェースを作成する方法についてのガイドライン(特に初心者向け)を書いています。この件についてサッターの両方の記事を読み、インターネットで例と回答を検索し、いくつかのテストを行いました。
このコードはコンパイルしてはいけません!
void foo(SomeInterface & a, SomeInterface & b)
{
SomeInterface c ; // must not be default-constructible
SomeInterface d(a); // must not be copy-constructible
a = b ; // must not be assignable
}
上記のすべての動作は、スライスの問題の原因を見つけます:抽象クラス(または階層内の非リーフクラス)は、派生クラスが可能な場合でも、構築もコピー/割り当てもできません。
0番目の解決策:基本的なインターフェース
class VirtuallyDestructible
{
public :
virtual ~VirtuallyDestructible() {}
} ;
このソリューションは単純で、やや素朴です:すべての制約に違反しています:デフォルトで構築、コピーで構築、およびコピーで割り当て可能それ)。
- デストラクタをインラインに維持する必要があるため、デストラクタを宣言できません。また、一部のコンパイラは、インラインの空のボディで純粋な仮想メソッドをダイジェストしません。
- はい、このクラスの唯一のポイントは、実装者を事実上破壊可能にすることです。これはまれなケースです。
- 追加の仮想純粋メソッド(大部分のケース)があったとしても、このクラスはコピー割り当て可能です。
だから、いや...
最初の解決策:boost :: noncopyable
class VirtuallyDestructible : boost::noncopyable
{
public :
virtual ~VirtuallyDestructible() {}
} ;
このソリューションは、プレーン、クリア、C ++(マクロなし)であるため、最適です。
問題は、VirtuallyConstructibleがまだデフォルトで構築されている可能性があるため、その特定のインターフェイスではまだ機能しないことです。
- デストラクタをインラインに維持する必要があるため、デストラクタを純粋な仮想として宣言することはできません。また、一部のコンパイラはそれをダイジェストしません。
- はい、このクラスの唯一のポイントは、実装者を事実上破壊可能にすることです。これはまれなケースです。
別の問題は、コピーできないインターフェイスを実装するクラスが、それらのメソッドを必要とする場合、コピーコンストラクターと代入演算子を明示的に宣言/定義する必要があることです(そして、コードには、クライアントからアクセスできる値クラスがあります)インターフェイス)。
これは、ゼロのルールに反します。これは、私たちが行きたいところです。デフォルトの実装で問題がなければ、それを使用できるはずです。
2番目の解決策:それらを保護します!
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
// With C++11, these methods would be "= default"
MyInterface() {}
MyInterface(const MyInterface & ) {}
MyInterface & operator = (const MyInterface & ) { return *this ; }
} ;
このパターンは、(少なくともユーザーコードでは)技術的な制約に従います。MyInterfaceをデフォルトで構築したり、コピーで構築したり、コピーで割り当てたりすることはできません。
また、クラスの実装に人為的な制約を課しません。これらのクラスは、ゼロの規則に従うことも、C ++ 11/14で問題なく「=デフォルト」としていくつかのコンストラクター/演算子を宣言することもできます。
現在、これは非常に冗長であり、代替手段は次のようなマクロを使用することです。
class MyInterface
{
public :
virtual ~MyInterface() {}
protected :
DECLARE_AS_NON_SLICEABLE(MyInterface) ;
} ;
保護対象はマクロの外側にある必要があります(スコープがないため)。
正しく「名前空間」(つまり、会社または製品の名前がプレフィックスとして付けられます)、マクロは無害です。
そして、利点は、すべてのインターフェイスにコピーペーストされるのではなく、コードが1つのソースに組み込まれることです。move-constructorとmove-assignmentが将来同じ方法で明示的に無効にされた場合、これはコードの非常に軽い変更になります。
結論
- インターフェイスでのスライシングからコードを保護したいのは妄想ですか?(私はそうではないと思うが、誰も知らない...)
- 上記の中で最良の解決策は何ですか?
- 別のより良い解決策はありますか?
これは(特に)初心者向けのガイドラインとなるパターンであるため、「各ケースに実装が必要」などのソリューションは実行可能なソリューションではないことに注意してください。
報奨金と結果
質問に回答するのに費やした時間と回答の関連性から、私はコアダンプへの賞金を授与しました。
問題に対する私の解決策は、おそらくそのようなものに行きます:
class MyInterface
{
DECLARE_CLASS_AS_INTERFACE(MyInterface) ;
public :
// the virtual methods
} ;
...次のマクロを使用します。
#define DECLARE_CLASS_AS_INTERFACE(ClassName) \
public : \
virtual ~ClassName() {} \
protected : \
ClassName() {} \
ClassName(const ClassName & ) {} \
ClassName & operator = (const ClassName & ) { return *this ; } \
private :
これは、次の理由で私の問題の実行可能な解決策です。
- このクラスはインスタンス化できません(コンストラクターは保護されています)
- このクラスは事実上破棄できます
- このクラスは、継承クラスに過度の制約を課すことなく継承できます(たとえば、継承クラスはデフォルトでコピー可能です)
- マクロを使用すると、インターフェイスの「宣言」が簡単に認識(および検索)され、そのコードが1か所に組み込まれ、変更が容易になります(適切な接頭辞の付いた名前は、望ましくない名前の衝突を削除します)
他の回答が貴重な洞察を与えたことに注意してください。試してくれた皆さん、ありがとう。
私はまだこの質問に別の報奨金を置くことができると思うことに注意してください、そして私はそれを見る必要があるので、その答えに割り当てるためだけに報奨金を開きます。
virtual ~VirtuallyDestructible() = 0
、インターフェイスクラスの仮想継承(抽象メンバーのみ)だけです。おそらく、VirtuallyDestructibleは省略できます。
virtual void bar() = 0;
例えば?これにより、インターフェイスがインスタンス化されなくなります。