C++ How come I can't assign a base class to a child class?
I have code like this:
class Base
{
public:
void operator = (const Base& base_)
{
}
};
class Child : public Base
{
public:
};
void func()
{
const Base base;
Child child;
child = base;
}
My question is: since Child derives from Base (hence it should inherit Base's operator= ), how come when the statement
child = base;
is executed, I get a compiler error like this:
>.\main.cpp(78) : error C2679: binary '=' : no operator found which takes a right-hand operand of type 'const Base' (or there is no acceptable conversion)
1> .\main.cpp(69): could be 'Child &Child::operator =(const Ch开发者_如何学运维ild &)'
1> while trying to match the argument list '(Child, const Base)'
The behavior that I want is for the Child class to recognize that it's being assigned a Base class, and just "automatically" call the its parent's operator=.
Once I added this code to the Child class
void operator = (const Base& base_)
{
Base::operator=(base_);
}
then everything compiled fine. Though I dont think this would be good because if I have like 5 different classes that inherit from Base, then I have to repeat the same code in every single derived class.
NOTE: My intention for copying the Base
to Child
is to simply copying the members that are common to both Base
and Child
(which would be all the members of Base
). Even after reading all of the answers below, I really don't see why C++ doesn't allow one to do this, especially if there's an explicit operator=
defined in the Base
class.
The standard provides the reason for your specific question in 12.8/10 "Copying class objects" (emphasis added):
Because a copy assignment operator is implicitly declared for a class if not declared by the user, a base class copy assignment operator is always hidden by the copy assignment operator of a derived class (13.5.3).
So since there's an implicitly declared operator=(const Child&)
when the compiler is performing the name lookup/overload resolution for a Child::operator=()
, the Base
class's function signature is never even considered (it's hidden).
The code below is the behavior I wanted since the beginning, and it compiles,
class Base
{
public:
void operator = ( const Base& base_)
{
}
};
class Child : public Base
{
};
void func()
{
const Base base;
Child child;
child.Base::operator=(base);
}
I never knew that you can explicitly call something like:
child.Base::operator=(base);
Anyway, I learned a lot. Thank you for all the people that posted answers here.
You can't assign Base to Child, because Child might not be base. To use the typical animal example, what you have here is:
const Animal animal;
Dog dog;
dog = animal;
But animal might be a cat, and you certainly can't assign a cat to a dog.
That being said, you couldn't do it the other way round either:
const Dog dog;
Animal animal;
animal = dog;
A dog certainly is an animal, but you have a size issue here. animal
takes up sizeof(Animal)
space on the stack, but dog
takes up sizeof(Dog)
space, and those two amounts may be different.
When working with polymorphism in C++, you need to be working with either references or pointers. For example, the following is fine:
Dog* const dog;
Animal* animal;
animal = dog;
Here, the size of a pointer is the same regardless of what it points to, so the assignment is possible. Note that you still need to maintain the correct hierarchy conversions. Even when using a pointer, you can't assign a Base to a Child, because of the reason I explained earlier: A Base may be something entirely different from the Child.
Because a base isn't a child. Consider:
class Child : public Base
{
public:
SomeFunc();
};
void func()
{
const Base base;
Child child;
child = base;
child.SomeFunc(); // whatcha gonna call?
}
UPDATE: TO answer the question in the comment, child.SomeFunc();
is perfectly legal -- it's only a problem if th value of child
isn't really a Child object. The compiler cannot allow child = base;
because it would create a situation where the legal call fails.
This isn't actually an answer, but the how-to question has been answered. This is the why-not.
Classes should mean something. There should be useful things you can say about any well-formed object in the class ("class invariants"), because that way you can reason about programs on the basis of how a class works. If a class is just a collection of data members, you can't do that, and have to reason about the program on the basis of each individual data element.
The proposed assignment operator will change every data member in the base class, but won't touch the ones defined in the child class. This means one of two things.
If a Child object is well-formed after another Base object is dumped into its Base component, it means that the additional data members have no real connection with the the Base data members. In that case, Child isn't really a subtype of Base, and public inheritance is the wrong relation. Child isn't in a "is-a" relation to Base, but rather a "has-a" relation. This is usually expressed by composition (Child has a Base data member), and can also be expressed by private inheritance.
If a Child object is not well-formed after another Base object is dumped into its Base component, the proposed assignment operator is a really fast and convenient method to mess up a variable.
For an example, let's have a Mammal base class and two child classes, Cetacean and Bat. Animal has things like size and weight, and the child classes have fields related to what they eat and how they move. We assign a Cetacean to a Mammal, which is reasonable (all Cetacean-specific fields are just dropped), and now we assign that Mammal to a Bat. Suddenly, we've got an twenty-ton bat.
With a well-designed set of classes, the only way I can conceive of this assignment operator even being thought of is if the child classes have no additional data members, only additional behavior, and even then it's suspect.
Therefore, my only advice is to redo your class design. Many people, particularly those familiar with Java and other languages that tend to deep inheritance hierarchies, use inheritance far too much in C++. C++ generally works best with a large number of base classes and a relatively shallow hierarchy.
When you don't declare copy assignment explicitly, the C++ compiler will create copy assignment for you . In your case, it would be
class Child : public Base
{
public:
Child& operator=(const Child& rhs) { ... }
};
And that's why you get this compiler error. Also, it makes more sense to return a reference to itself when you provide a copy assignment because it will allow you to chain operator together.
It's not a good idea to declare a copy assignment like this.
void operator = (const Base& base_)
{
Base::operator=(base_);
}
I don't see why you only want to copy the base class portion when you do a copy assignment.
The answer is scarier than I thought. You can do this. The standard evens has a little note about it in section 7.3.3 The main problem is that the operator= that gets defined by default in Child hides the operator= from Base so you need to import it back into the class scope using "using".
This compiles OK for me. However I find this idea very scary, and potentially fraught with undefined behaviour if stuff in Child needs to get initialised.
class Base
{
public:
void operator = (const Base& base_)
{
}
};
class Child : public Base
{
public:
using Base::operator=;
};
int main()
{
const Base base = Base();
Child child;
child = base;
}
EDIT: Made the base const again and avoid "uninitialised const" error.
The problem is that you can't assign a constant value to a non-constant variable. If you could, you could potentially modify the constant value by modifying the non-constant variable.
Is it necessary that base is const? I ask because you're really running into two problems: const correctness, and inheritence/polymorphism.
EDIT: I'm second guessing myself now regarding my first statement. Any clarification?
精彩评论