Reputation: 2361
The following simplified (yet compileable) example illustrates a possible slice-assignment scenario.
#include <string>
struct Base
{
// Mutating method. Not a chance of making it virtual.
template <typename Anything>
Base& operator=(const Anything& x)
{
m_int = x.AsInteger();
return *this;
}
int AsInteger() const
{
return m_int;
}
int m_int;
};
struct Derived : public Base
{
template <typename Anything>
Derived& operator=(const Anything& x)
{
m_text = x.AsString();
Base::operator=(x);
return *this;
}
const std::string& AsString() const
{
return m_text;
}
// Invariant: Derived::m_text matches Base::m_x.
std::string m_text;
};
void ExamineBase(const Base* b)
{
b->AsInteger();
}
void ExamineBase(const Base& b)
{
b.AsInteger();
}
void InitBase(Base* b)
{
*b = Base();
}
void InitBase(Base& b)
{
b = Base();
}
int main()
{
Base b;
InitBase(b); // <----- (1)
InitBase(&b); // <----- (2)
Derived d;
Derived& ref = d;
Derived* ptr = &d;
ExamineBase(ref); // <----- (3)
ExamineBase(ptr); // <----- (4)
InitBase(ref); // <----- (5)
InitBase(ptr); // <----- (6)
return 0;
}
Lines (1), (2), (3) and (4) are good.
Lines (5) and (6) exhibit a problem: they change only the base subobject within a complete object, obviously breaking the coherence between Base::m_int and Derived::m_text.
I'm interested in preventing such slice-modification from happening, yet preserving the validity of lines (1), (2), (3) and (4).
So the questions are:
a) Are there any tricks that may prevent calling non-const member functions of a base class through a pointer to a derived class?
b) Are there any tricks that may block standard implicit conversion from Derived*
to Base*
, but still allow conversion from Derived*
to const Base*
?
Upvotes: 3
Views: 278
Reputation:
Disclaimer: I am answering the question as asked, but if you are wondering how to achieve this, then odds are more than likely that something is wrong with your design.
Short answer: this cannot be done with public inheritance, full stop. The whole point of public inheritance is that a referenece or pointer to an object of Derived
can be used as a reference or pointer to an object of Base
, regardless of context.
So, the way to do this would be to go through private inheritance, or a member variable, and only expose the Base
member through an accessor returning a const
reference or pointer:
#include <string>
struct Base
{
// Mutating method. Not a chance of making it virtual.
template <typename Anything>
Base& operator=(const Anything& x)
{
m_int = x.AsInteger();
return *this;
}
int AsInteger() const
{
return m_int;
}
int m_int;
};
struct Derived : private Base
{
template <typename Anything>
Derived& operator=(const Anything& x)
{
m_text = x.AsString();
Base::operator=(x);
return *this;
}
const std::string& AsString() const
{
return m_text;
}
const Base& base() const {return *this;}
// Invariant: Derived::m_text matches Base::m_x.
std::string m_text;
};
void ExamineBase(const Base* b)
{
b->AsInteger();
}
void ExamineBase(const Base& b)
{
b.AsInteger();
}
void InitBase(Base* b)
{
*b = Base();
}
void InitBase(Base& b)
{
b = Base();
}
int main()
{
Base b;
InitBase(b); // <----- (1)
InitBase(&b); // <----- (2)
Derived d;
Derived& ref = d;
Derived* ptr = &d;
ExamineBase(ref.base()); // <----- (3)
ExamineBase(&ptr->base()); // <----- (4)
InitBase(ref.base()); // <----- BOOM!
InitBase(&ptr->base()); // <----- BOOM!
return 0;
}
Upvotes: 2