Why is an assignment to a base class valid, but an assignment to a derived class a compilation error?

前端 未结 6 1405
旧巷少年郎
旧巷少年郎 2021-01-31 08:27

This was an interview question. Consider the following:

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

Why does b = a;

相关标签:
6条回答
  • 2021-01-31 08:57

    If I am getting interviewed then, I will explain in little philosophical way.

    a = b;
    

    is valid, because every B contains A as its part. So a can extract A from within B. However, A doesn't contain B. thus b cannot find B from within A; that's why,

    b = a;
    

    is invalid.

    [Analogically, a void* can be found in any Type*, but Type* cannot be found in void* (thus we need a cast).]

    0 讨论(0)
  • 2021-01-31 09:03

    Because the implicitly declared copy assignment operator of B hides the implicitly declared copy assignment operator of A.

    So for the line b = a, only the the operator= of B is a candidate. But its parameter has type B const&, which cannot be initialized by an A argument (you would need a downcast). So you get an error.

    0 讨论(0)
  • 2021-01-31 09:03

    Because every B is an A, but not every A is a B.

    Edited following comments to make things a bit clearer (I modified your example):

    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;
    

    In your example, there's no attributes someInt nor anotherInt, so it could work. But the compiler will not allow it anyway.

    0 讨论(0)
  • 2021-01-31 09:03

    It's true that a B is an A, but an A is not a B, but this fact is only directly applicable when you're working with pointers or references to A's and B's. The problem here is your assignment operator.

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

    Is equivalent to

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

    So when you're assigning below:

    A a; 
    B b; 
    a = b;
    

    The assignment operator on a can be called with an argument of b, because a B is an A, so b can be passed to the assignment operator as an A&. Note that a's assignment operator only knows about the data that's in an A, and not the stuff in a B, so any members of B that aren't part of A get lost - this is known as 'slicing'.

    But when you're trying to assign:

    b = a; 
    

    a is of type A, which is not a B, so a can't match the B& parameter to b's assignment operator.

    You would think that b=a should just call the inherited A& A::operator=(const A&), but this is not the case. The assignment operator B& B::operator=(const B&) hides the operator that would be inherited from A. It can be restored again with a using A::operator=; declaration.

    0 讨论(0)
  • 2021-01-31 09:06

    Remember that if there's not an explicitly declared copy-assignment operators one will be implicitly declared and defined for any class (and structs are classes in C++).

    For struct A it'll have the following signature:

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

    And it simply performs memberwise assignment of its subobjects.

    a = b; is OK because B will match with the const A& parameter for A::operator=(const A&). Since only members of A are 'memberwise assigned' to the target, any members of B that aren't part of A get lost - this is known as 'slicing'.

    For struct B the implcit assignment operator will have the following signature:

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

    b = a; is not OK because A won't match the const B& argument.

    0 讨论(0)
  • 2021-01-31 09:07

    I've changed the names of your structs to make the reason obvious:

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

    Clearly, any Bear is also an Animal, but not every Animal can be considered a Bear.

    Because every B "isa" A, any instance of B must also be an instance of A: by definition it has the same members in the same order as any other instance of A. Copying b into a loses the B-specific members, but completely fills the members of a resulting in a struct that satisfies the requirements of A. Copying a to b, on the other hand, may leave b incomplete because B could have more members than A. That's hard to see here because neither A nor B has any members at all, but this is why the compiler allows one assignment and not the other.

    0 讨论(0)
提交回复
热议问题