私の関数の1つは、ベクトルをパラメーターとして受け取り、それをメンバー変数として格納します。以下に説明するように、ベクトルへのconst参照を使用しています。
class Test {
public:
void someFunction(const std::vector<string>& items) {
m_items = items;
}
private:
std::vector<string> m_items;
};
ただし、items
に多数の文字列が含まれている場合があるため、移動セマンティクスをサポートする関数を追加(または関数を新しい関数に置き換え)したいと思います。
私はいくつかのアプローチを考えていますが、どれを選ぶべきかわかりません。
1)unique_ptr
void someFunction(std::unique_ptr<std::vector<string>> items) {
// Also, make `m_itmes` std::unique_ptr<std::vector<string>>
m_items = std::move(items);
}
2)値を渡して移動する
void someFunction(std::vector<string> items) {
m_items = std::move(items);
}
3)右辺値
void someFunction(std::vector<string>&& items) {
m_items = std::move(items);
}
どのアプローチを避けるべきですか、そしてその理由は何ですか?
ベクターがヒープ上に存在する理由がない限り、_unique_ptr
_の使用はお勧めしません。
ベクトルの内部ストレージはとにかくヒープ上に存在するため、_unique_ptr
_を使用する場合は、2度の間接参照が必要になります。1つはベクトルへのポインターを逆参照し、もう1つは内部ストレージバッファーを逆参照します。
そのため、2または3のいずれかを使用することをお勧めします。
オプション3(右辺値参照が必要)を使用する場合、someFunction
を呼び出すときに、クラスのユーザーに右辺値を渡す(一時値から直接、または左辺値から移動する)という要件を課しています。
左辺値から移動する要件は面倒です。
ユーザーがベクターのコピーを保持したい場合は、フープを飛び越えてそうする必要があります。
_std::vector<string> items = { "1", "2", "3" };
Test t;
std::vector<string> copy = items; // have to copy first
t.someFunction(std::move(items));
_
ただし、オプション2を選択した場合、ユーザーはコピーを保持するかどうかを決定できます。選択は自分で行います。
コピーを保管してください:
_std::vector<string> items = { "1", "2", "3" };
Test t;
t.someFunction(items); // pass items directly - we keep a copy
_
コピーを保持しないでください:
_std::vector<string> items = { "1", "2", "3" };
Test t;
t.someFunction(std::move(items)); // move items - we don't keep a copy
_
表面的には、オプション2は、左辺値と右辺値の両方を1つの関数で処理するため、良いアイデアのように思われます。ただし、ハーブサッターがCppCon 2014の講演で述べているように、 基本に戻る!現代のC++スタイルの要点 、これは、左辺値の一般的なケースの悲観論です。
m_items
がitems
よりも「大きい」場合、元のコードはベクトルにメモリを割り当てません。
// Original code:
void someFunction(const std::vector<string>& items) {
// If m_items.capacity() >= items.capacity(),
// there is no allocation.
// Copying the strings may still require
// allocations
m_items = items;
}
std::vector
のコピー代入演算子は、既存の代入を再利用するのに十分賢いです。一方、パラメータを値で取得するには、常に別の割り当てを行う必要があります。
// Option 2:
// When passing in an lvalue, we always need to allocate memory and copy over
void someFunction(std::vector<string> items) {
m_items = std::move(items);
}
簡単に言うと、コピーの作成と割り当てのコストは必ずしも同じではありません。コピーの割り当てがコピーの作成よりも効率的である可能性は低くありません—std::vector
およびstd::string
の方が効率的です。 †。
Herbが指摘しているように、最も簡単な解決策は、右辺値のオーバーロードを追加することです(基本的にはオプション3)。
// You can add `noexcept` here because there will be no allocation‡
void someFunction(std::vector<string>&& items) noexcept {
m_items = std::move(items);
}
コピー割り当ての最適化は、m_items
がすでに存在する場合にのみ機能するため、値によるコンストラクターへのパラメーターの取得はまったく問題ありません。割り当てはどちらの方法でも実行する必要があります。
TL; DR:addオプション3を選択します。つまり、左辺値に対して1つのオーバーロードと1つのオーバーロードがあります。右辺値の場合。オプション2は、コピー割り当ての代わりにコピー構築を強制します。これは、よりコストがかかる可能性があります(std::string
およびstd::vector
用です)
†オプション2が悲観的である可能性があることを示すベンチマークを見たい場合、 話のこの時点で 、ハーブはいくつかのベンチマークを示しています
‡std::vector
のムーブ代入演算子がnoexcept
でない場合、これをnoexcept
としてマークするべきではありませんでした。カスタムアロケータを使用している場合は、 ドキュメント を参照してください。
経験則として、タイプのムーブ代入がnoexcept
の場合にのみ、同様の関数にnoexcept
のマークを付ける必要があることに注意してください。
それはあなたの使用パターンに依存します:
オプション1
長所:
短所:
unique_ptr
_を使用してラップされていない限り、これによって読みやすさが向上することはありません。vector
は1つになる必要があります。標準ライブラリコンテナは、値の格納に内部割り当てを使用する管理対象オブジェクトであるため、これは、そのようなベクトルごとに2つの動的割り当てがあることを意味します。 1つは一意のptr + vector
オブジェクト自体の管理ブロック用で、もう1つは保存されたアイテム用です。概要:
_unique_ptr
_を使用してこのベクトルを一貫して管理している場合は、引き続き使用してください。そうでない場合は使用しないでください。
オプション2
長所:
このオプションは、発信者がコピーを保持しないかどうかを決定できるため、非常に柔軟性があります。
_std::vector<std::string> vec { ... };
Test t;
t.someFunction(vec); // vec stays a valid copy
t.someFunction(std::move(vec)); // vec is moved
_
呼び出し元がstd::move()
を使用すると、オブジェクトは2回だけ移動されます(コピーはありません)。これは効率的です。
短所:
std::move()
を使用しない場合、一時オブジェクトを作成するために常にコピーコンストラクターが呼び出されます。 void someFunction(const std::vector<std::string> & items)
を使用し、_m_items
_がすでにitems
を収容するのに十分な大きさであった場合、割り当て_m_items = items
_は追加の割り当てなしでのコピー操作。概要:
このオブジェクトがreになることを事前に知っている場合-実行時に何度も設定され、呼び出し元は常にstd::move()
、私はそれを避けていただろう。それ以外の場合、これは非常に柔軟性があり、問題のあるシナリオにもかかわらず、要求に応じて使いやすさと高いパフォーマンスの両方を可能にするため、優れたオプションです。
オプション3
短所:
このオプションは、発信者に自分のコピーをあきらめることを強制します。したがって、自分自身にコピーを保持したい場合は、追加のコードを作成する必要があります。
_std::vector<std::string> vec { ... };
Test t;
t.someFunction(std::vector<std::string>{vec});
_
概要:
これはオプション#2よりも柔軟性が低いため、ほとんどのシナリオで劣っていると思います。
オプション4
オプション2と3の短所を考えると、追加のオプションを提案すると思います。
_void someFunction(const std::vector<int>& items) {
m_items = items;
}
// AND
void someFunction(std::vector<int>&& items) {
m_items = std::move(items);
}
_
長所:
短所:
概要:
そのようなプロトタイプがない限り、これは素晴らしいオプションです。
これに関する現在のアドバイスは、ベクトルを値で取得し、それをメンバー変数に移動することです。
_void fn(std::vector<std::string> val)
{
m_val = std::move(val);
}
_
そして、私はちょうどチェックしました、_std::vector
_はムーブ代入演算子を提供します。呼び出し元がコピーを保持したくない場合は、呼び出しサイトの関数fn(std::move(vec));
に移動できます。