简体   繁体   English

具有值语义的运行时多态类

[英]Run-time polymorphic class with value semantics

I would like a class Value , which both has a run-time polymorphic behaviour, and a value semantics. 我想要一个类Value ,它具有运行时多态行为和值语义。 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 . 注意 :使用升压或C ++ 11智能指针在这里是不希望的:它们使呼叫者代码冗长,使用->代替. , and do not have copy constructors or assignment operators implementing a true value semantics. ,并且没有实现真值语义的复制构造函数或赋值运算符。 Also, this question doesn't target specifically containers. 此外,这个问题并不针对具体的容器。

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? 很难知道你想要在这里实现什么,但首先猜测(即将推出的)Boost Type Erasure库似乎是合适的?

any<
    mpl::vector<
        copy_constructible<>,
        typeid_<>,
        incrementable<>,
        ostreamable<>
    >
> x(10);
++x;
std::cout << x << std::endl; // prints 11

(Example from docs ). (来自docs的例子)。

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: 这个想法是通过一个多态类ValueImpl的指针来存储实际的实现,该指针提供您需要的任何虚拟方法,例如increments()print() ,以及一个方法clone() ,以便您的类Data能够实现值语义:

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. 包含的指针存储在C ++ 11 std::unique_ptr<ValueImpl>以确保在销毁或分配新值时释放内存。

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. 这提供了运行时多态性与值语义,而不是传统的运行时多态性与C ++语言内置的指针语义。 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++. 事实上,多态性的概念(处理根据其真实“类型”行为不同的通用对象)独立于指针的概念(能够通过使用对象的地址共享内存和优化函数调用),以及恕我直言对于实现细节,多态仅在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. 注意:感谢CaptainObvlious提供的贡献以及我此部分集成的演进代码。 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 您可能更喜欢使用抽象接口,而不是我的非抽象基类

polymorphic_value has been proposed for standardisation and has some of the semantics you require. polymorphic_value已被提议用于标准化,并具有您需要的一些语义。 You'll have to define your own operator << though. 不过,您必须定义自己的operator <<

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>可以保存从T公开派生的类的对象,并且复制polymorphic_value将复制派生类型的对象。

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> . polymorphic_value<T>通过类型擦除实现,并使用编译器生成的派生对象的复制构造函数正确复制存储为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. 无需使用或定义自定义clone方法。

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 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 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 https://github.com/jbcoe/polymorphic_value

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM