2

Value実行時のポリモーフィック動作と値セマンティクスの両方を備えた class が必要です。たとえば、次のようなことができるようにしたいと考えています。

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

それは可能ですか?

注:ここではブーストまたは 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()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++11 内に格納さstd::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++ のポインターを介してのみ提供されます。上記のコードは、ポインターの使用が「哲学的に必要」ではない場合にポリモーフィズムを利用するための回避策であり、メモリ管理を容易にします。

注: 貢献してくれた CaptainObvlios と、私が部分的に統合した彼の進化したコードをここで入手できたことに感謝します。統合されていないものは次のとおりです。

  • 派生実装の作成を容易にするために、中間テンプレート クラスを作成することができます。
  • 私の非抽象基本クラスの代わりに抽象インターフェースを使用することを好むかもしれません
于 2013-06-18T01:59:59.203 に答える