web-dev-qa-db-ja.com

C ++不変クラスを宣言する慣用的な方法

そのため、主なデータ型が不変の構造体/クラスである非常に広範な機能コードがあります。私が不変性を宣言している方法は、メンバー変数とメソッドをconstにすることにより、「実質的に不変」です。

struct RockSolid {
   const float x;
   const float y;
   float MakeHarderConcrete() const { return x + y; }
}

これは実際にC++で「行う必要がある」方法ですか?または、より良い方法がありますか?

33
BlamKiwi

コードでRockSolid変数の割り当てを行う必要がある場合を除いて、提案した方法はまったく問題ありません。

RockSolid a(0,1);
RockSolid b(0,1);
a = b;

コンパイラによってコピー割り当て演算子が削除されたため、これは機能しません。

したがって、別の方法は、プライベートデータメンバを持ち、パブリックconst関数のみを持つクラスとして構造体を書き換えることです。

class RockSolid {
  private:
    float x;
    float y;

  public:
    RockSolid(float _x, float _y) : x(_x), y(_y) {
    }
    float MakeHarderConcrete() const { return x + y; }
    float getX() const { return x; }
    float getY() const { return y; }
 }

このように、RockSolidオブジェクトは(疑似)不変ですが、割り当てを行うことはできます。

32
chrphb

あなたの目標は真の不変性だと思います-構築された各オブジェクトは変更できません。あるオブジェクトを別のオブジェクトに割り当てることはできません。

デザインの最大の欠点は、移動セマンティクスと互換性がないことです。これにより、このようなオブジェクトを返す関数がより実用的になります。

例として:

_struct RockSolidLayers {
  const std::vector<RockSolid> layers;
};
_

これらのいずれかを作成できますが、作成する関数がある場合:

_RockSolidLayers make_layers();
_

その内容を(論理的に)戻り値にコピーするか、_return {}_構文を使用して直接構築する必要があります。外では、次のいずれかを行う必要があります。

_RockSolidLayers&& layers = make_layers();
_

または(論理的に)コピー構築。移動構築できないことは、最適なコードを得るためのいくつかの簡単な方法の妨げになります。

現在、これらのコピー構成は両方とも省略されていますが、C++には「破棄して移動」操作がないため、名前付きオブジェクト間でデータを移動できませんmoveどちらも変数をスコープ外に取り出し、それを使用して他の何かを構築します。

そして、C++が破壊前にオブジェクト(たとえば、_return local_variable;_)を暗黙的に移動する場合は、constデータメンバーによってブロックされます。

不変のデータを中心に設計された言語では、その(論理的な)不変性にもかかわらず、データを「移動」できることがわかります。

この問題を解決する1つの方法は、ヒープを使用し、データを_std::shared_ptr<const Foo>_に保存することです。これで、constnessはメンバーデータではなく、変数にあります。また、上記の_shared_ptr<const Foo>_を返す各タイプのファクトリー関数のみを公開し、他の構築をブロックすることもできます。

このようなオブジェクトは、Barが_std::shared_ptr<const Foo>_メンバーを格納して構成できます。

_std::shared_ptr<const X>_を返す関数はデータを効率的に移動でき、ローカル変数は、「実際の」データを台無しにせずに処理が完了すると、状態を別の関数に移動できます。

関連する手法の場合、制約の少ないC++では、そのような_shared_ptr<const X>_を取得し、不変ではないふりをするラッパータイプ内に格納するのが理想的です。変異操作を行うと、_shared_ptr<const X>_が複製および変更され、保存されます。最適化は、_shared_ptr<const X>_が「本当に」_shared_ptr<X>_であることを「認識」します(注:ファクトリー関数が_shared_ptr<X>_を_shared_ptr<const X>_にキャストすることを確認するか、これは実際には真ではありません)、およびuse_count()が1の場合、代わりにconstを捨て去り、直接変更します。これは、「コピーオンライト」として知られる技術の実装です。