2

我想要一个类Value,它既具有运行时多态行为,又具有值语义。例如,我希望能够执行以下操作:

// 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();

有可能吗,如果有,怎么做?

注意:这里不希望使用 boost 或 C++11 智能指针:它们使调用者代码变得冗长,使用->代替.,并且没有复制构造函数或赋值运算符实现真正的值语义。此外,这个问题并不专门针对容器。

4

3 回答 3

3

很难知道你想在这里实现什么,但乍一看似乎(即将推出的)Boost Type Erasure 库可能是合适的?

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

(来自文档的示例)。

于 2013-06-18T02:24:39.980 回答
3

polymorphic_value已被提议用于标准化,并具有您需要的一些语义。不过,您必须定义自己的operator <<

Apolymorphic_value<T>可以持有从 T 公开派生的类的对象,复制 polymorphic_value 将复制派生类型的对象。

polymorphic_value<T>使用类型擦除实现,并使用派生对象的编译器生成的复制构造函数来正确复制存储为polymorphic_value<BaseType>.

定义了复制构造函数和赋值运算符,以便对象是类似值的。无需使用或定义自定义clone方法。

简单来说:

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.
};

控制块的特化允许定义其他构造函数。

动机和设计在这里讨论:

https://github.com/jbcoe/polymorphic_value/blob/master/talks/2017_1_25_cxx_london.md

和这里

https://github.com/jbcoe/polymorphic_value/blob/master/draft.md

可以在此处找到带有测试的完整实现:

https://github.com/jbcoe/polymorphic_value

于 2017-01-28T00:16:37.377 回答
2

是的,有可能,但是当然必须有一些隐藏指针,而且实际数据必须存储在堆上。原因是在编译时无法知道数据的实际大小,因此无法在堆栈上。

这个想法是通过一个多态类的指针来存储实际的实现ValueImpl,它提供了你需要的任何虚拟方法,比如increments()or 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(); }
};

包含的指针存储在 C++11std::unique_ptr<ValueImpl>中,以确保在销毁或分配新值时释放内存。

派生的实现最终可以通过以下方式定义:

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_;
};

这足以使问题中的代码片段无需任何修改即可工作。这提供了具有值语义的运行时多态性,而不是具有 C++ 语言内置的指针语义的传统运行时多态性。实际上,多态性的概念(根据其真实“类型”处理行为不同的通用对象)独立于指针的概念(能够通过使用对象的地址共享内存和优化函数调用),恕我直言更多的是实现细节,多态性仅通过 C++ 中的指针提供。上面的代码是在“哲学上不需要”使用指针时利用多态性的一种解决方法,从而简化了内存管理。

注意:感谢 CaptainObvlious 的贡献和他在此处提供的进化代码,我部分集成了这些代码。未集成的有:

  • 为了简化派生实现的创建,您可能需要创建一个中间模板类
  • 您可能更喜欢使用抽象接口而不是我的非抽象基类
于 2013-06-18T01:59:59.203 回答