Reputation: 7748
I would like a class Value
, which both has a run-time polymorphic behaviour, and a value semantics. For instance, I would like to be able to do things like:
// create polymorphic data
Value v1 = IntValue(42);
Value v2 = DoubleValue(12.3);
// copy-by-value semantics
Value v3 = v1;
v3.increments();
Value v4;
v4 = v2;
v4.increments();
// possibly put them in my favourite container
MyList<Value> l;
l << v1 << v2 << v3 << v4;
// print them: "Int(42) Double(12.0) Int(43) Double(13.0) "
for(int i=0; i<l.size(); i++) l[i].print();
Is it possible, and if yes, how?
Note: Using boost or C++11 smart pointers as here is not desired: they make the caller code verbose, use ->
instead of .
, and do not have copy constructors or assignment operators implementing a true value semantics. Also, this question doesn't target specifically containers.
Upvotes: 3
Views: 1976
Reputation: 3921
polymorphic_value
has been proposed for standardisation and has some of the semantics you require. You'll have to define your own operator <<
though.
A polymorphic_value<T>
may hold a an object of a class publicly derived from T, and copying the polymorphic_value will copy the object of the derived type.
polymorphic_value<T>
is implemented with type erasure and uses the compiler-generated copy-constructor of the derived objects to correctly copy objects stored as polymorphic_value<BaseType>
.
Copy constructors and assignment operators are defined so that the objects are value-like. There is no need to use or define a custom clone
method.
In brief:
template <class T>
struct control_block
{
virtual ~control_block() = default;
virtual T* ptr() = 0;
virtual std::unique_ptr<control_block> clone() const = 0;
};
template <class T>
class polymorphic_value {
std::unique_ptr<control_block<T>> cb_;
T* ptr_ = nullptr;
public:
polymorphic_value() = default;
polymorphic_value(const polymorphic_value& p) :
cb_(p.cb_->clone())
{
ptr_ = cb_->ptr();
}
T* operator->() { return ptr_; }
const T* operator->() const { return ptr_; }
T& operator*() { return *ptr_; }
const T& operator*() const { return *ptr_; }
// Some methods omitted/deferred.
};
Specializations of the control block allow other constructors to be defined.
Motivation and design is discussed here :
https://github.com/jbcoe/polymorphic_value/blob/master/talks/2017_1_25_cxx_london.md
and here
https://github.com/jbcoe/polymorphic_value/blob/master/draft.md
A full implementation with tests can be found here:
https://github.com/jbcoe/polymorphic_value
Upvotes: 5
Reputation: 7748
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:
Upvotes: 2
Reputation: 4523
It's hard to know what you're trying to achieve here, but at first guess it seems that the (upcoming) Boost Type Erasure library might be suitable?
any<
mpl::vector<
copy_constructible<>,
typeid_<>,
incrementable<>,
ostreamable<>
>
> x(10);
++x;
std::cout << x << std::endl; // prints 11
(Example from docs).
Upvotes: 3