Recently, I encountered some code that allows users to use an object of type Base
(not Base &
or Base *
!) to “hold” a value of some class derived from Base
. The code looks like this:
#include <iostream>
#include <unordered_map>
class Base {
protected:
unsigned _code;
public:
Base(const unsigned code = -1u) : _code(code) {}
virtual ~Base() = default;
// An example of a method.
virtual void foo() const {
// Dispatch based on `type()`.
auto proto = entries().find(type());
if (proto != entries().end()) {
return proto->second->reinterpret(this).foo();
}
}
public:
operator unsigned() const { return _code; }
unsigned type() const { return _code & type_flag(-1u); }
unsigned payload() const { return _code & ~type(); }
protected:
static constexpr unsigned type_flag(const unsigned v) { return v << 24; }
using prototype = std::unordered_map<unsigned, Base *>;
static prototype &entries() {
static prototype m;
return m;
}
virtual Base &reinterpret(const Base *const x) const {
return *new (const_cast<Base *>(x)) Base(*x);
}
};
class Derived0 : public Base {
public:
static constexpr unsigned type = type_flag(0);
Derived0(const unsigned payload) : Base(type | payload) {}
Derived0(const Base &x = {}) : Base(x) {}
virtual void foo() const override {
std::cout << "Derived0::foo()" << std::endl;
}
protected:
virtual Base &reinterpret(const Base *const x) const override {
return *new (const_cast<Base *>(x)) Derived0(*x);
}
static __attribute__((constructor)) void init() {
entries()[type] = new Derived0;
};
};
class Derived1 : public Base {
public:
static constexpr unsigned type = type_flag(1);
Derived1(const unsigned payload) : Base(type | payload) {}
Derived1(const Base &x = {}) : Base(x) {}
virtual void foo() const override {
std::cout << "Derived1::foo()" << std::endl;
}
protected:
virtual Base &reinterpret(const Base *const x) const override {
return *new (const_cast<Base *>(x)) Derived1(*x);
}
static __attribute__((constructor)) void init() {
entries()[type] = new Derived1;
};
};
int main() {
const Derived0 d0(42);
const Derived1 d1(43);
Base b;
b = d0;
std::cout << "b.type() = " << b.type() << std::endl; // 0 << 24
std::cout << "b.payload() = " << b.payload() << std::endl; // 42
b.foo(); // Derived0::foo()
b = d1;
std::cout << "b.type() = " << b.type() << std::endl; // 1 << 24
std::cout << "b.payload() = " << b.payload() << std::endl; // 43
b.foo(); // Derived1::foo()
}
I have several questions regarding this code fragment. First, is there a name for this design pattern? I tried looking for the “prototype pattern” following the variable names, but the prototype pattern looks very dissimilar to this.
Second, is it the case that using const Base
will trigger UB in almost every use cases? For example, according to my understanding,
const Base b = Derived0(42);
b.foo();
will modify the const
object b
through the placement new
in the reinterpret
method, which is UB (https://en.cppreference.com/w/cpp/language/const_cast). And if so, is there a way to fix this problem?