13

推奨されるコーディング手法について質問があります。モデル分析用のツールがあり、大量のデータを渡す必要がある場合があります (ファクトリ クラスから複数の異種チャンクを保持するクラスまで)。

私の質問は、ポインターを使用するか、所有権を移動するかについてコンセンサスがあるかどうかです (データ ブロックのサイズが 1 GB になる可能性があるため、可能な限りコピーを避ける必要があります)。

ポインター バージョンは次のようになります。

class FactoryClass {
...
public:
   static Data * createData() {
      Data * data = new Data;
      ...
      return data;
   }
};

class StorageClass {
   unique_ptr<Data> data_ptr;
...
public:
   void setData(Data * _data_ptr) {
      data_ptr.reset(_data_ptr);
   }
};

void pass() {
   Data * data = FactoryClass::createData();
   ...
   StorageClass storage;
   storage.setData(data);
}

一方、移動バージョンは次のようになります。

class FactoryClass {
...
public:
   static Data createData() {
      Data data;
      ...
      return data;
   }
};

class StorageClass {
   Data data;
...
public:
   void setData(Data _data) {
      data = move(_data);
   }
};

void pass() {
   Data data = FactoryClass::createData();
   ...
   StorageClass storage;
   storage.setData(move(data));
}

私は move バージョンの方が気に入っています - そうです、move コマンドをメイン コードに追加する必要がありますが、最終的にはストレージ内のオブジェクトだけになり、ポインター セマンティクスを気にする必要がなくなります。

しかし、私は詳細を理解していない移動セマンティクスを使用すると、あまりリラックスできません。(ただし、コードは既に Gcc4.7+ でしかコンパイルできないため、C++11 の要件は気にしません)。

いずれかのバージョンをサポートするリファレンスを誰かが持っていますか? または、データを渡す方法の他の推奨バージョンはありますか?

キーワードは通常他のトピックにつながるため、Google で何かを検索することはできませんでした。

ありがとう。

編集注: 2 番目の例は、コメントからの提案を組み込むためにリファクタリングされましたが、セマンティクスは変更されていません。

4

1 に答える 1

11

オブジェクトを関数に渡す場合、渡す内容は、その関数がそれをどのように使用するかによって部分的に異なります。関数は、次の 3 つの一般的な方法のいずれかでオブジェクトを使用できます。

  1. オブジェクトの所有権を維持する呼び出し元の関数 (または呼び出しスタックの最終的な親) を使用して、関数呼び出しの間オブジェクトを参照するだけです。この場合の参照は、定数参照または変更可能な参照の場合があります。関数はこのオブジェクトを長期間保存しません。

  2. オブジェクトを直接コピーできます。オリジナルの所有権を取得することはありませんが、オリジナルのコピーを取得して、保存、変更、またはコピーの処理を行います。#1 とこれの違いは、コピーがパラメーター リストで明示的に作成されることです。たとえば、std::string値を取得します。intしかし、これはby 値を取得するのと同じくらい簡単な場合もあります。

  3. オブジェクトの所有権を何らかの形式で取得できます。関数は、オブジェクトの破棄に対して何らかの責任を負います。これにより、関数はオブジェクトを長期間保存することもできます。

これらのパラダイムのパラメーター タイプに対する私の一般的な推奨事項は次のとおりです。

  1. 可能であれば、明示的な言語参照によってオブジェクトを取得します。それが不可能な場合は、 を試してくださいstd::reference_wrapper。それが機能せず、他の解決策が妥当と思われない場合は、ポインターを使用してください。ポインターは、オプションのパラメーター (ただし、C++14 の std::optional により、あまり役に立たなくなります。ポインターにはまだ用途があります)、言語配列 (ただし、これらの用途のほとんどをカバーするオブジェクトがあります) のようなものになります。 )など。

  2. オブジェクトを値で取得します。それはかなり交渉の余地がありません。

  3. 値の移動 (つまり、値によるパラメーターに移動) またはオブジェクトへのスマート ポインター(とにかくコピー/移動するため、これも値によって取得されます) のいずれかによってオブジェクトを取得します。 . コードの問題は、ポインタを介して所有権を譲渡しているが、生のポインタを使用していることです。生のポインターには所有権のセマンティクスがありません。pointerを割り当てた瞬間に、すぐに何らかのスマート ポインターでラップする必要があります。したがって、ファクトリ関数はunique_ptr.

あなたのケースは#3のようです。バリュームーブとスマートポインターのどちらを使用するかは、完全にあなた次第です。なんらかの理由でヒープを割り当てる必要がある場合Data、選択はほとんどあなたのために行われます。Dataスタックを割り当てることができる場合、いくつかのオプションがあります。

Data私は通常、の内部サイズの見積もりに基づいてこれを行います。内部的には、ほんの数個のポインター/整数 (「少数」とは、3 ~ 4 のような意味) である場合は、スタックに置くことは問題ありません。

実際、ダブルキャッシュミスの可能性が少なくなるため、より良い結果が得られます。Data関数が別のポインターからデータにアクセスすることが多い場合、ポインターで保存する場合Data、その関数呼び出しはすべて、保存されたポインターを逆参照して内部ポインターをフェッチしてから、内部ポインターを逆参照する必要があります。どちらのポインターも との局所性を持たないため、これは 2 つの潜在的なキャッシュ ミスですStorageClass

値で保存する場合、の内部ポインターが既にキャッシュに存在するData可能性が高くなります。の他のメンバーとのData局所性が優れています。StorageClass以前にアクセスStorageClassしたことがある場合は、キャッシュ ミスの代償を払っているため、既にキャッシュにある可能性がありますData

しかし、移動は自由ではありません。完全なコピーよりも安価ですが、無料ではありません。あなたはまだ内部データをコピーしています(そして、元のポインタを無効にする可能性があります)。ただし、ヒープへのメモリの割り当ても無料ではありません。割り当てを解除することもありません。

ただし、頻繁に移動しない場合 (最終的な場所に移動するために移動しますが、その後は少し移動します)、より大きなオブジェクトを移動しても問題ありません。オブジェクトを移動するよりも使用している場合は、オブジェクトのストレージのキャッシュの局所性が、移動のコストよりも勝る可能性があります。

最終的に、どちらかを選択する技術的な理由はあまりありません。合理的な場合はデフォルトで移動することをお勧めします。

于 2013-07-29T20:13:49.120 に答える