私はいくつかのレガシーコードを調べていますが、開発者が追加のメンバーで既存のクラスを「拡張」し、選択した武器として継承を使用しているケースに遭遇しました。基本的には次のようになります。
struct A
{
int something_old;
};
struct B : public A
{
int something_new;
};
Aのインスタンスがあるコードのセクションがありますが、実際にはBのインスタンスが必要です。これを実現する方法を以下に示します。
auto aa = GetAn_A(); // for example
:
B bb;
*(A*)&bb = aa; // <-- code smell?
bb.something_new = 42;
これは問題なく動作するようです。bbをスライスしてから、スライスされた部分に割り当てます。しかし、私はこれらの賢明なC++の時代では、ハッキングの「n」と「&」および「*」によるスライスはすべて少し古い学校だと考えざるを得ません。
ここにコードのにおいがあり、もしあれば、どれくらいのにおいがありますか?これはまだ書く必要がありますか?
もしそれがis臭い場合、作者はどこでプロットを失い始めましたか、そして彼らが望む結果を得るために何ができた/すべきか、すなわち既存のクラスへの拡張(AとBは実際にはこれよりもかなり複雑であり、AはBの前に書かれたことを覚えておいてください)?
A
とB
の意味と意味によって異なります。
基本クラスのサブオブジェクトは、メンバーサブオブジェクトができないこと(空であること、型のレイアウトを妨げないこと、メンバー関数を所有者として自動的に公開することなど)を実行できるため、ポリモーフィズムがなくてもクラスからの継承が意味をなす場合があります。
上記の場合、どちらの型もポリモーフィックではないため、動的でないポリモーフィックの目的で継承が使用されています。つまり、ユーザーの意図は、B
をA
であるかのように扱うことではありません。
基本的に、ユーザーはB
の基本クラスをメンバーサブオブジェクトのように扱います。タイプA
の値を返す関数があり、タイプB
のオブジェクトに保存したいとします。メンバーではなく基本クラスであるという事実は、本質的に実装の詳細です。
C++ 17はこの解釈をある程度正規化していることに注意してください。あなたの例では、B
はC++ 17の集約であり、集約初期化を使用してそのすべてのサブオブジェクトを初期化できます。
B bb = {aa, 42};
この種のものは非常に便利です。基本クラスのメソッドをクラスのメンバーにする機能は、それをメンバーにすると簡単には実行できません。たとえば、string_view
のようないくつかの追加のメンバーを持つクラスのように、継承するときに数十個の転送関数を作成するのは、それから派生するだけの場合は愚かです。パブリック継承は少しやり過ぎですが、誰もが一連のusing
宣言を書きたいとは限りません。
継承は、パブリック継承であっても、常に「である」という意味ではありません。
今でも、割り当ての処理方法が原因で、コードレビューで失敗します。 (A&)bb = aa;
。
@NicolBolasに加えて優れた答え。コンストラクターを使用して、キャストせずにそのシーケンスを実行できます。
...
A aa = GetA();
...
B bb(aa); // instead of: B bb; *(A*)&bb = aa;
...
このため、AとBには適切なコンストラクターが必要です(これらのコンストラクターも厄介なキャストを必要としないことに注意してください)。