Yes, it is possible, but of course there must be some hidden pointer, and the actual data must be stored on the heap. The reason is that the actual size of the data cannot be known at compile-time, and then can't be on the stack.
The idea is to store the actual implementation through a pointer of a polymorphic class ValueImpl
, that provides any virtual method you need, like increments()
or print()
, and in addition a method clone()
, so that your class Data
is able to implement the value semantics:
class ValueImpl
{
public:
virtual ~ValueImpl() {};
virtual std::unique_ptr<ValueImpl> clone() const { return new ValueImpl(); }
virtual void increments() {}
virtual void print() const { std::cout << "VoidValue "; }
};
class Value
{
private:
ValueImpl * p_; // The underlying pointer
public:
// Default constructor, allocating a "void" value
Value() : p_(new ValueImpl) {}
// Construct a Value given an actual implementation:
// This allocates memory on the heap, hidden in clone()
// This memory is automatically deallocated by unique_ptr
Value(const ValueImpl & derived) : p_(derived.clone()) {}
// Destruct the data (unique_ptr automatically deallocates the memory)
~Value() {}
// Copy constructor and assignment operator:
// Implements a value semantics by allocating new memory
Value(const Value & other) : p_(other.p_->clone()) {}
Value & operator=(const Value & other)
{
if(&other != this)
{
p_ = std::move(other.p_->clone());
}
return *this;
}
// Custom "polymorphic" methods
void increments() { p_->increments(); }
void print() { p_->print(); }
};
The contained pointer is stored inside a C++11 std::unique_ptr<ValueImpl>
to ensure the memory is released when destroyed or assigned a new value.
The derived implementations can finally be defined the following way:
class IntValue : public ValueImpl
{
public:
IntValue(int k) : k_(k) {}
std::unique_ptr<IntValue> clone() const
{
return std::unique_ptr<IntValue>(new IntValue(k_));
}
void increments() { k_++; }
void print() const { std::cout << "Int(" << k_ << ") "; }
private:
int k_;
};
class DoubleValue : public ValueImpl
{
public:
DoubleValue(double x) : x_(x) {}
std::unique_ptr<DoubleValue> clone() const
{
return std::unique_ptr<DoubleValue>(new DoubleValue(k_));
}
void increments() { x_ += 1.0; }
void print() const { std::cout << "Double(" << x_ << ") "; }
private:
int x_;
};
Which is enough to make the code snippet in the question works without any modification. This provides run-time polymorphism with value semantics, instead of the traditional run-time polymorphism with pointer semantics provided built-in by the C++ language. In fact, the concept of polymorphism (handling generic objects that behave differently according to their true "type") is independent from the concept of pointers (being able to share memory and optimize function calls by using the address of an object), and IMHO it is more for implementation details that polymorphism is only provided via pointers in C++. The code above is a work-around to take advantage of polymorphism when using pointers is not "philosophically required", and hence ease memory management.
Note: Thanks for CaptainObvlious for the contribution and his evolved code available here that I partially integrated. Not integrated are:
- To ease the creation of derived implementations, you may want to create an intermediate templated class
- You may prefer to use an abstract interface instead of my non-abstract base class