はい、可能ですが、もちろん隠しポインターが必要であり、実際のデータはヒープに格納する必要があります。その理由は、コンパイル時にデータの実際のサイズを知ることができず、スタック上に置くことができないためです。
アイデアは、クラスが値のセマンティクスを実装できるように、必要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 と、私が部分的に統合した彼の進化したコードをここで入手できたことに感謝します。統合されていないものは次のとおりです。
- 派生実装の作成を容易にするために、中間テンプレート クラスを作成することができます。
- 私の非抽象基本クラスの代わりに抽象インターフェースを使用することを好むかもしれません