一時的な非コンスト参照に関するC ++制限を回避する
-
03-10-2019 - |
質問
他の計算に必要な「スクラッチパッド」であるC ++データ構造があります。それは長寿命ではなく、頻繁に使用されていないため、パフォーマンスが重要ではありません。ただし、他の更新可能な追跡フィールドの中には乱数ジェネレーターが含まれており、ジェネレーターの実際の値は重要ではありませんが、 は コピーして再利用するのではなく、値が更新されることが重要です。 これは、一般に、このクラスのオブジェクトが参照によって渡されることを意味します。
インスタンスが1回だけ必要な場合、最も自然なアプローチは、必要な場所(おそらく工場の方法またはコンストラクターを使用する)を構築し、scratchpadを消費方法に渡すことです。消費者の方法署名は、これが唯一の用途であることを知っていないため、参照ごとにPassを使用しますが、工場の方法とコンストラクターは価値によって戻ります。
厄介な一時的な変数でコードを詰まらせないようにする方法はありますか?次のようなことを避けたい:
scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);
xyz.initialize_computation(useless_temp);
私は本質的にスクラッチパッドを作ることができました mutable
すべてのパラメーターにラベルを付けるだけです const &
, 、しかし、それは誤解を招くので、私は最高の実践ではありません。完全に制御しないクラスのためにこれを行うことはできません。 RValueの参照を通過するには、ScratchPadのすべての消費者に過負荷を追加する必要があります。これは、目的を打ち負かし、明確で簡潔なコードを使用します。
パフォーマンスが重要ではないという事実を考えると(ただし、コードサイズと読みやすさは)、 このようなスクラッチパッドを渡すための最良のアプローチは何ですか? C ++ 0x機能を使用すると問題ありません 必要 しかし、できればC ++ 03のみの機能で十分です。
編集: 明確にするために、一時的なものを使用することは実行可能です。それは私が避けたいコードの不幸な混乱です。一時的な名前を決して与えない場合、それは明らかに一度だけ使用され、読むべきコードの行が少ないほど良いです。また、コンストラクターの初期化剤では、一時的な宣言を宣言することは不可能です。
解決
rvaluesを非const参照を受け入れる関数に渡すことは大丈夫ではありませんが、rvaluesでメンバー関数を呼び出すことは大丈夫ですが、メンバー関数はそれがどのように呼ばれたかを知りません。現在のオブジェクトへの参照を返すと、rvaluesをlvaluesに変換できます。
class scratchpad_t
{
// ...
public:
scratchpad_t& self()
{
return *this;
}
};
void foo(scratchpad_t& r)
{
}
int main()
{
foo(scratchpad_t().self());
}
通話方法に注意してください self()
ただし、lalue式が得られます scratchpad_t
rvalueです。
私が間違っている場合は私を修正してください。しかし、RValue参照パラメーターはLValue参照を受け入れないので、それらを使用するにはScratchPadのすべての消費者に過負荷を追加する必要がありますが、これも残念です。
まあ、あなたはテンプレートを使用できます...
template <typename Scratch> void foo(Scratch&& scratchpad)
{
// ...
}
あなたが電話するなら foo
rvalueパラメーターを使用して、 Scratch
に推測されます scratchpad_t
, 、 したがって Scratch&&
そうなるでしょう scratchpad_t&&
.
そして、あなたが電話するなら foo
lvalueパラメーターを使用して、 Scratch
に推測されます scratchpad_t&
, 、そして参照崩壊ルールのために、 Scratch&&
またそうです scratchpad_t&
.
正式なパラメーターに注意してください scratchpad
そのタイプがlvalue参照であるかrvalueリファレンスであるかに関係なく、名前であるため、lvalueです。合格したい場合 scratchpad
他の関数については、これらの関数のテンプレートトリックは必要ありません。LValue参照パラメーターを使用するだけです。
ちなみに、あなたはに関係する一時的なスクラッチパッドが xyz.initialize_computation(scratchpad_t(1, 2, 3));
すぐに破壊されます initialize_computation
完了ですよね?内部に参照を保存します xyz
後のユーザーのオブジェクトは非常に悪い考えです。
self()
メンバーメソッドである必要はありません、それはテンプレートされた関数になることができます
はい、それも可能ですが、私はそれを名前に変更して、意図をより明確にするために次のように変更します。
template <typename T>
T& as_lvalue(T&& x)
{
return x;
}
他のヒント
これだけの問題です:
scratchpad_t<typeX<typeY,potentially::messy>, typename T> useless_temp = factory(rng_parm);
醜いですか?もしそうなら、なぜこれを変えてみませんか?:
auto useless_temp = factory(rng_parm);
個人的には、私はむしろ見たいです const_cast
よりも mutable
. 。私が見るとき mutable
, 、私は誰かが論理的にやっていると仮定しています const
- 性、そしてそれをあまり考えないでください。 const_cast
ただし、このようなコードが必要とするように、赤い旗を上げます。
1つのオプションは、ようなものを使用することです shared_ptr
(auto_ptr
何に応じて動作します factory
実行している)そしてそれを価値で渡します。これにより、コピーコストを回避し、単一のインスタンスのみを維持しますが、工場の方法から渡すことができます。
ヒープにオブジェクトを割り当てると、コードを次のようなものに変換できる可能性があります。
std::auto_ptr<scratch_t> create_scratch();
foo( *create_scratch() );
工場は作成して返されます auto_ptr
スタック内のオブジェクトの代わりに。返された auto_ptr
一時的なものはオブジェクトの所有権を取得しますが、一時的に非コンストメソッドを呼び出すことが許可されており、実際の参照を取得するためにポインターを繰り返すことができます。次のシーケンスポイントでは、スマートポインターが破壊され、記憶が解放されます。同じことを渡す必要がある場合 scratch_t
さまざまな関数を連続して、スマートポインターをキャプチャするだけです。
std::auto_ptr<scratch_t> s( create_scratch() );
foo( *s );
bar( *s );
これを置き換えることができます std::unique_ptr
今後の標準で。
Fredoverflowの応答は、メソッドを使用して単純に非コンストリファレンスを返すという彼の提案の答えとしてマークしました。これはC ++ 03で機能します。そのソリューションには、スクラッチパッドのようなタイプごとにメンバーメソッドが必要ですが、C ++ 0xでは、あらゆるタイプに対してより一般的にその方法を記述することもできます。
template <typename T> T & temp(T && temporary_value) {return temporary_value;}
この関数は、単に通常のlvalue参照を転送し、rvalue参照をlvalue参照に変換します。もちろん、これを行うと、結果が無視されている修正可能な値が返されます。