web-dev-qa-db-ja.com

状態を記録するためにラムダ関数またはブール変数を使用する方が良いですか

ブール状態変数は一般的に悪いため、可能な場合は回避する必要があると主張する人もいます。明らかに多くの場合、ブール値を使用する代わりに状態をラムダ関数に入れることが可能です。このアプローチの例は、 C#のLazy の概念的な実装です。 _Lazy<T>_は、最初にGet()が呼び出されたときにTを構築する関数のようなオブジェクトを取得します。 Get()への後続の呼び出しは、同じTオブジェクトを繰り返し返します。

たとえば、boolを使用するLazyの簡単な実装は次のようになります これでSO answer 。簡単な(スレッドunsafe)バージョン:

_class Lazy<T>
{
    private readonly Func<T> createValue;
    private bool isValueCreated;
    private T value;

    T Get()
    {
       if (!isValueCreated)
       {
           value = createValue();
           isValueCreated = true;
       }
       return value;
    }

    public Lazy(Func<T> createValue)
    {
        this.createValue = createValue;
    }    
}
_

代わりの方法は、ブール値を削除し、状態を保存する方法としてラムダ関数を使用することです。

_class Lazy<T>
{

    private Func<T> valueGetter;

    public Lazy(Func<T> createValue)
    {
        valueGetter = () =>
        {
            T value = createValue();
            valueGetter = () => value;
            return value;
        };
    }
    public T Get() { return valueGetter(); }
}
_

どちらのバリアントも同じように読みやすいと思いますが、2番目のバリアントは短いとはいえ、一部のプログラマにとっては2番目のバリアントは意外なものになる場合があります。サプライズ要因のため、私は2番目のバリアントをあまり好まない。 C++でも同じことができます。

_template <class T>
class Lazy
{
  public:
    template <class U>
    Lazy(U createValue)
    {
        valueGetter = [this, createValue]() {
            T value = createValue();
            valueGetter = [value] { return value; } ;
            return value;
        };
    }
    T get() { return valueGetter(); }

 private:

    std::function<T()> valueGetter;
};
_

誰もが上記を提案するのを見たことがないので、この構成をC++で使用するのはそれほど流行ではないと思います。

上記のラムダは、「ニートトリック」の種類の方が魅力的ですが、「他の人との共同作業では魅力的ではありません。 「考え方。 ラムダをそのように使用できる場合はブール値を回避する必要がありますか?そのようなラムダの使用は良いデザインと見なされていますか?CおよびC++ 98をプログラミングしすぎて、ブール値を好むというこれらの言語の考え方に汚染されていますか?

6
Michael Veksler

これらの種類のオブジェクト指向または機能的なテクニックを使用すると、非常にきちんとエレガントになります。ここでやっていることに凝った名前が必要な場合は、状態を表す関数オブジェクトを使用してState Patternをお勧めします。

しかし、そのようなアプローチには2つの反対意見があります。

  • それらはあまり明白ではありません。シンプルがいい。自分自身を別のクロージャーに再割り当てするクロージャーは、厳密には単純ではありません。ここでは、削除するよりも複雑さが増しているようです。

  • これらは間接的なレベルが追加されることを意味するため、効率が低下します。この手法では、最適化が困難な間接呼び出しが必要になります。対照的に、特に条件が偽である可能性が高いことをコンパイラーに通知できる場合、条件文は最適化が非常に簡単です。

ただし、これは状況によって異なります。ブールフィールドを追加すると、オブジェクトの本体が大きくなり、キャッシュの使用率が低下するため、パフォーマンスへの影響が大きくなります。ランタイムによって提供されるLazy実装は、タグ付きポインターなどのトリックや、タイプを(レイジーサンクからターゲット値に)変更する可能性のあるオブジェクトによって、スペースのオーバーヘッドを回避できる可能性があります。

ブール値を使用する代わりに、値Tをnull以外の参照型に制限することもできます。次に、オブジェクトを構築する必要があるかどうかを判断するには、nullチェックで十分です。

8
amon