TL;DR: Yes this is well defined in what it does BUT what it does may not be exactly what you want in all cases -- you need to carefully design your classes to ensure it doesn't cause problems.
The code
Base* parent = &child;
*parent = Base(3); // change base only
is well-defined in what it does -- it calls the Base (move) assignment operator, whatever that may be. For simple object like you've defined, this is just fine -- the default assignment generated for these classes will simply assign (just) the base fields. The derived field (and the actual type) of the child object will not be affected in anyway.
However, the fact that this can be called this way has implications for the design of any class that can be inherited from, or any class that inherits from it.
- The Base assignment operator needs to be aware of the fact that it might be called on a derived class instance, or with an argument that is a reference to a derived class instance (or both! and they might even be different derived classes!)
- The derived class needs to be aware of the fact that base class assignment (and methods) may be called, which may be relevant if it is trying to maintain some invariants between base class fields an derived class fields.
For example, consider an attempt to inherit from a pimpl class:
class Base {
protected:
class Impl {
// implementation details
public:
virtual Impl *clone() const { return new Impl(*this); }
} *impl;
Base(Impl *i) : impl(i) {}
public:
Base() : impl(new Impl) {}
Base(const Base &a) : impl(a.impl->clone()) {}
Base(Base &&a) : impl(a.impl) { a.impl = nullptr; }
Base &operator=(const Base &a) {
if (this != &a) {
delete impl;
impl = a.impl->clone(); }
return *this; }
Base &operator=(Base &&a) {
std::swap(impl, a.impl);
return *this; }
~Base() { delete impl; }
// other public methods, generally call impl->whatever
};
class Derived : public Base {
protected:
class Impl : public Base::Impl {
// implementation details
Impl *clone() const { return new Impl(*this); }
}
public:
Derived : Base(new Impl) {}
void foo() {
/* we "know" this will always be a Derived */
static_cast<Impl *>(impl)->some_derived_impl_method();
}
};
At first glance, this may seem ok, but it turns out the comment in Derived::foo is wrong. If someone goes and does:
Derived child;
Base *parent = &child;
*parent = Base();
child.foo().
bad things will happen. You can fix this by changing the assignment operator to carefully check the type of *impl
and the object being assigned, but that can get very complex fast when there are multiple derived classes that might exist.