Consider the below code, a derived class replaces a virtual member function with a variant of the function, but does not add any new member variables. Values of Base and Derived are added to a common container, std::vector and as expected the Derived value is sliced. However by copying the representation in memory of the Derived value into the container the value is in effect only partially sliced.
#include <iostream>
#include <vector>
class Base {
public:
Base() = default;
Base(float arg) : a{ arg } {};
virtual float doSomething(float b) const { return a + b; }
float a;
};
class Derived : public Base {
public:
Derived() = default;
Derived(float a) : Base{ a } {};
float doSomething(float b) const { return a - b; }
};
int main()
{
Base b{ 1.0f };
Derived d{ 1.0f };
std::cout << sizeof(b) << ", " << sizeof(d) << '\n'; // 8, 8
std::vector<Base> v{ b, d }; // d is sliced
std::cout << v[0].doSomething(2.0f) << '\n'; // 3
std::cout << v[1].doSomething(2.0f) << '\n'; // 3 as d was sliced
memcpy(&v[1], &d, sizeof(d)); // Copy the representation of d over to v[1]
std::cout << v[1].doSomething(2.0f) << '\n'; // now -1
}
The size of the values is 8 due to the pointer to the virtual function table and this is how to above polymorphism is being realised. The type of v[1] is always Base, so if Derived added a new member function it wouldn't be possible to call it. In effect v[1] is still sliced to Base but with the reimplemented member functions of Derived.
Under the assumption that Base is essentially POD but with added virtual member functions, all of which are const i.e. memory copyable, and that Derived only reimplements those member functions:
Starting with your questions:
- Does the above code fall into undefined behaviour?
Yes. Use of memcpy
on non-trivially copyable objects is undefined behavior.
- If so is there a way to implement this without the memcpy or equivalent in a way that would be defined behaviour?
Yes, there is. It will still use polymorphism - not for the object that you store but rather for its field(s).
- If this is a common pattern, what is it called?
Yes. The proposed solution has a name. It is called Strategy-Pattern or State-Pattern (depending on what is exactly the purpose of what you are trying to achieve).
Here is an equivalent code (in a way) to what you try to achieve:
class Base {
public:
virtual ~Base() {}
virtual float doSomething(float a, float b) const { return a + b; }
};
class Derived : public Base {
public:
float doSomething(float a, float b) const override { return a - b; }
};
class RealType {
float a;
const Base* strategy;
public:
// just for the example, could be implemented in other ways
const static Base BaseStrategy;
const static Derived DerivedStrategy;
RealType(float val, const Base& s): a(val), strategy(&s) {}
float doSomething(float b) const { return strategy->doSomething(a, b); }
};
const Base RealType::BaseStrategy {};
const Derived RealType::DerivedStrategy {};
int main()
{
RealType b{ 1.0f, RealType::BaseStrategy };
RealType d{ 1.0f, RealType::DerivedStrategy };
std::cout << sizeof(b) << ", " << sizeof(d) << '\n'; // size of pointer
std::vector<RealType> v{ b, d }; // no slicing
std::cout << v[0].doSomething(2.0f) << '\n'; // 3
std::cout << v[1].doSomething(2.0f) << '\n'; // -1 as no slicing
v[0] = v[1]; // copies both the value stored in v[1] as well as the strategy
std::cout << v[0].doSomething(2.0f) << '\n'; // now -1 with v[0]
}