2011-07-01 14 views
38

これはインタビューの質問でした。次のことを考えてみましょう:基本クラスへの割り当ては有効ですが、派生クラスへの割り当てにはコンパイルエラーがあるのはなぜですか?

struct A {}; 
struct B : A {}; 
A a; 
B b; 
a = b; 
b = a; 

a = b;はまったく問題ありながら、なぜb = a;は、エラーをスローしますか?

+1

エラーは何ですか? – MGZero

+3

これは実際には非常に良い質問です。最初の声明がうまくいく理由はすぐに分かりません。 –

+0

私は構造体がクラスのように振る舞うと仮定しています。それは、一意の 'B'構造体アイテムをすべて切り捨て、すべての 'a'をコピーします。 – DanTheMan

答えて

62

暗黙的に宣言されたコピー代入演算子Bは暗黙的に宣言されたコピー代入演算子Aを隠すためです。

したがって、b = aの場合は、operator=Bのみが候補です。しかし、そのパラメータの型はで、A引数で初期化することはできません(ダウンキャストが必要です)。だからあなたはエラーが発生します。

+3

これは正解です。 – Puppy

+14

'B'の定義を' struct B:A {A :: operator =;を使って変更することができます。 };そして、両方の行がコンパイルされることを確認してください。 –

+0

良い意味合いがあります。他の答えはより哲学的ですが、これは動作しない特定の技術的理由が含まれています。 –

24

すべてのBはAですが、すべてのAは(私はあなたの変形例)物事が少し明確にするために、コメントを次B.

編集されていないので:、あなたの例では

struct A {int someInt;}; 
struct B : A {int anotherInt}; 
A a; 
B b; 

/* Compiler thinks: B inherits from A, so I'm going to create 
    a new A from b, stripping B-specific fields. Then, I assign it to a. 
    Let's do this! 
*/ 
a = b; 

/* Compiler thinks: I'm missing some information here! If I create a new B 
    from a, what do I put in b.anotherInt? 
    Let's not do this! 
*/ 
b = a; 

属性はsomeIntでもanotherIntでもないので、となる可能性があります。しかし、コンパイラはそれをとにかく許可しません。

+8

-1:哲学的には、あなたの答えは正しいですが、哲学はオブジェクトを参照するポインタを使って話しているときにのみコードに直接変換されます。暗黙的な 'A :: operator ='と 'B :: operator =' missingについての技術的な詳細があります。 –

+1

+1は愚かなdownvotesには、彼らが説明されても関係なく –

+0

@ LokiAstari:あなたは間違っています。答えは正解です.C++の技術はそれほどではありません。他の言語もわずかに異なる方法で同じことをします。細部を記憶し、「なぜ」を理解していないよりも、なぜ「技術的なC++レベルの詳細」ではなく「なぜ」を理解する方が良いでしょう。 litbの答えは、「理由」部門では少し欠けています。 –

4

私は理由を明らかにするために、あなたの構造体の名前を変更しました:

struct Animal {}; 
struct Bear : Animal {}; 
Animal a; 
Bear b; 
a = b; // line 1 
b = a; // line 2 

は明らかに、任意のクマは、動物ではなく、すべての動物がクマとみなすことができます。

すべてのB "isa" AのインスタンスもAのインスタンスでなければならない:定義上、Aの他のインスタンスと同じ順序で同じメンバを持つ。bをaにコピーすると、一方、aからbをコピーすると、BがAより多くのメンバーを持つ可能性があるため、bが不完全なままになる可能性があります。 AもBもメンバーをまったく持っていませんが、これはコンパイラーが1つの割り当てを許可し、他の割り当てを許可しない理由です。

+2

これはCicadaの答えと同じ理由で間違っています。その説明を参照してください。ああ、それは他の理由(LSP)でも間違っています。 –

+2

名前を変更してもそれ以上は分かりません。 –

+0

矩形/正方形の継承は、継承が "is-a"のように逆転する典型的な例です。Rectangleは代わりにSquareのサブクラスである必要があると主張できます。あなたはより良い例を挙げることができますか? –

1

私がインタビューを受けているなら、私は少しの哲学的方法で説明します。

a = b; 

すべてのBはその一環としてA含まれているため、有効です。したがって、aは、BからAを抽出できます。ただし、AにはBが含まれていません。したがってbAからBを見つけることができません。その理由は、

b = a; 

は無効です。明示的に宣言コピー代入演算子1人の意志がない場合は、[類推、(したがって、私たちはキャストが必要)void*はどのType*で見つけることができますが、Type*void*で見つけることができません。]

+0

これも**間違っています。最初の変換は損失です(つまり、情報が失われます)。だからなぜそれは有効ですか?そうすべきではない。あなたの類推は、 'void * 'への変換が損失ではないので、欠陥があります。 –

+4

@Konrad、私は同意しない。情報は失われますが、bオブジェクトのB固有の部分はオブジェクトが有効なAであるために必要ではないため、B固有のメンバをスライスすることができます。これは非仮想関数に問題を引き起こすため、C++標準の議論の対象となっています。プレーンなデータオブジェクトの場合、問題はありません。 –

+1

@Martin平らなデータオブジェクトでさえ、この損失を被ります。 'double'から' int'への(許可された)暗黙の変換を考えてください。 C++がこれを可能にするという事実はまったく問題ではありません。現代の言語のほとんどは危険なので、これを禁止しています。 –

3

があることを忘れないでください暗黙的に宣言され、任意のクラス(構造体はC++のクラス)に定義されます。

struct Aためには、次のシグネチャがあります:

A& A::operator=(const A&) 

をそして、それは単にそのサブオブジェクトのmemberwise割り当てを行います。

a = b;Bは、const A&のパラメータがA::operator=(const A&)と一致するため、問題ありません。 Aのメンバーだけがターゲットに 'memberwise assigned'されているため、AのメンバーではないBのメンバーが失われます。これは「スライシング」として知られています。 implcit代入演算子は、次のシグネチャがありますstruct Bについては

Aconst B&引数が一致しないため

B& B::operator=(const B&) 

b = a;はOKではありません。

6

BAですが、ABはありませんが、A年代とB年代にポインタまたは参照で作業しているときに、この事実が唯一の直接適用可能であることそれは本当です。ここでの問題は代入演算子です。 Bがあるのでaの代入演算子は、bの引数で呼び出すことができます

A a; 
B b; 
a = b; 

:だから、あなたは以下の代入しているとき

struct A {}; 
struct B : A {}; 

struct A { 
    A& operator=(const A&); 
}; 
struct B : A { 
    B& operator=(const B&); 
}; 

と同等ですAなので、bA&として代入演算子に渡すことができます。 aの代入演算子は、Aのデータのみを知り、Bのデータは知っていないので、Aの一部ではないBのメンバーはすべて失われます。これは「スライシング」として知られています。

しかし、あなたが割り当てしようとしている:

b = a; 

aBされていない、タイプAであるので、abの代入演算子へB&パラメータと一致することはできません。

b=aは、継承されたA& A::operator=(const A&)を呼び出すべきだと思うでしょうが、そうではありません。代入演算子B& B::operator=(const B&)は、Aから継承される演算子を非表示にします。それはusing A::operator=;宣言で再び復元できます。