How valid is it to assign a new object to a base one, without changing the inherited ones?
The example below works as expected, but is it by chance?
This example is simple, but is there any situation where something can break, such as when there are virtual functions, pointers, and other more complex C++ stuff?
#include <iostream>
struct Base
{
Base(int x) : base_data(x){}
int base_data;
};
struct Concrete : public Base
{
Concrete(int x, int y) : Base(x), concrete_data(y){}
int concrete_data;
};
int main()
{
Concrete child(1, 2);
std::cout << child.base_data << std::endl; // 1
std::cout << child.concrete_data << std::endl; // 2
Base* parent = &child;
*parent = Base(3); // change base only
std::cout << child.base_data << std::endl; // 3 (changed)
std::cout << child.concrete_data << std::endl; // 2 (remains the same)
return 0;
}
7
As mentioned in the comments, this line:
*parent = Base(3); // change base only
Is 100% valid and well defined.
The result is that the base class – Base
– members will be updated in the *parent
object (despite the fact that is it actually a Concrete
object i.e. an instance of a derived class).
Since you also asked about virtual methods:
The vtable holding the pointers to the virtual methods (which is BTW an implementation details and not specified as such in the standard) should not be overwritten.
Therefore the virtual methods of Concrete
will be called (because this was and still is the type of the actual object).
Therfore the behavior you observe is the expected one.
Complete example including a virtual method:
#include <iostream>
struct Base {
Base(int x) : base_data(x) {}
int base_data;
virtual void m() { std::cout << "Base::m()n"; }
};
struct Concrete : public Base {
Concrete(int x, int y) : Base(x), concrete_data(y) {}
int concrete_data;
void m() override { std::cout << "Concrete::m()n"; }
};
int main() {
Concrete child(1, 2);
std::cout << child.base_data << std::endl; // 1
std::cout << child.concrete_data << std::endl; // 2
child.m();
Base* parent = &child;
*parent = Base(3); // change base only
std::cout << child.base_data << std::endl; // 3 (changed)
std::cout << child.concrete_data << std::endl; // 2 (remains the same)
child.m();
}
Output:
1
2
Concrete::m()
3
2
Concrete::m()
Live demo
A side note:
Although defined, such code is somewhat confusing (which I guess is the reason for you question).
I recoemmend to do it only if you have a good reason, that you did not explain.