6

関数型言語で使用されるものと同様に、C++ で null 伝播演算子を実装した人はいますか? おそらく operator-> の伝播動作に似た、巧妙なテンプレート ソリューションを考えています。

foo->bar->baz(すみません、デメテルの法則) のように、グラフから一連のオブジェクトがあるとします。これらのいずれかが null になる可能性があり、逆参照する前にテストする必要があるとします。その後、コードは突然さらに複雑になります。

if( !foo )
  return nullptr;
if( !foo->bar )
  return nullptr;
return foo->bar->baz;

次のようなコンパクトな構文を使用して、null チェックを「除外」したいと思います。

foo?->bar?->baz   // imaginary null-propagation syntax

もちろん、そのように見える必要はありません。ほぼ同じくらいコンパクトである必要があります。必要なのは C++ のモナドで、null テストと「継続」を有効にすることだと思います。マクロとラムダを避けるのは素晴らしいことですが、それはおそらく不可能です。this各ステップで operator->() をオーバーロードし、ifが null の場合は短絡することを想像できました。

しかし、それは非常に邪魔です。理想的なソリューションは、チェーン内の各オブジェクトをラップすることです。

4

3 に答える 3

10

デザインのこの段階では、これは根本的な出発点かもしれませんが、Null Object Patternの使用を検討してください。その後、null チェックはまったく必要ありません。

于 2013-05-01T00:09:06.777 に答える
1

C# には 2 種類の null 伝播があります。1つ目はあなたが説明したとおりです。ここで、がnullの場合fooはnullを返しますが、nullでfooない場合はを返しfoo->barます。

これは、C++ ですでにそれほど複雑でも読みにくくもありません。

return (foo) ? (foo->bar) ? foo->bar->baz : nullptr : nullptr;

上記の方法でインライン条件をネストできるようにするマクロを作成できないようです。

#define Maybe(X, Y) (X)?X->Y:nullptr
// Attempted nested usage:
return Maybe(foo, Maybe(foo->bar, baz));
//     ^ VS2015 complains here saying "Expected a member name".

しかし、マクロがいかに冗長であるかを考えると、たとえそれが機能したとしても、元々使用されていたインライン条件よりも優れているとは思えません。

ただし、Mooing Duck が回答で指摘した「二重呼び出し」の問題を回避するテンプレート ソリューションもあります。

template<typename T_Return, typename T_X, T_Return* T_X::*Y>
T_Return* Maybe(T_X* pX)
{
    return (pX) ? pX->*Y : nullptr;
}

//Usage:
// Type is required, so assume the struct Foo contains a Bar* and Bar contains a Baz*.
return Maybe<Baz, Bar, &Bar::baz>(Maybe<Bar, Foo, &Foo::bar(foo));

もちろん、これは要件を満たしていますが、ばかげていて鈍いです。

ただし、メソッドの呼び出しに使用される 2 番目のタイプの Null Propagation があることに注意してください。つまり、"else" ケースは必要ありません。これは次と同等です。

// C# code here.
if(foo != null) { foo.Bar(); }

C# では次のことができます。

foo?.Bar()

これは、マクロを使用して複製を試みるのがはるかに簡単な機能であり、通常の構文によりよく「適合」します。

// C++ Null Propagation
#define Maybe(X) if (auto tempX = (X)) tempX

//Usage
Maybe(GetFoo())->Bar();

//Or for setting a variable:
Maybe(GetFoo()->bar = new Bar();

?: 省略形を使用しておらず、代わりに通常のインライン if ステートメントを使用しているため、機能します。

これは、メソッドの呼び出しまたはメンバーの設定のみを目的としていることに注意してください。

ただし、本当にしつこい場合は、次のようなことができます。

#define ReturnMaybe(X, Y) auto tempX = X; \
if (tempX != nullptr) \
{ \
    return tempX->Y; \
} \
else \
{ \
    return nullptr; \
} \

次のようにインライン化できます。

#define ReturnMaybe(X, Y) auto tempX = X; if(tempX) { return tempX->Y; }else{return nullptr;}

// Usage
Bar* GetBar()
{
    ReturnMaybe(GetFoo(), bar)
}

しかし、これを連鎖させることはできず、ほぼすべての可能性 (おそらくメソッド呼び出しの null 伝播を除く) の冗長性を考慮すると、上記のソリューションのいずれかで問題を正確に解決することはできません。

編集 - テンプレート化された回答をわずかに改善しました。

編集 2 - これについて同僚と話していて、いくつかの他のオプションを考え出しました:

#define Maybe(X) (X==nullptr) ? nullptr : X

使用法:

return Maybe(foo)->bar;

興味深いことに、これは呼び出しメソッドの場合にも機能します。

Maybe(foo)->Bar();

展開するものが有効であるため (奇妙ではありますが)

(foo == nullptr) ? nullptr : foo->Bar();

ただし、?: 省略形で変数宣言を入れることができないため、保護することはできません。

return Maybe(get_next_parent())->child;

Mooing Duck が説明したように、一時的なものを自分で作成する必要があります。

于 2016-11-28T15:12:34.820 に答える
1

tldr; C++ がそれを実行できるかどうか、私は真剣に疑っています。

残念ながら、(プリプロセッサなしで)それができるとは思いません。私のコードは常に次のようになります

type function(type parent) {
    if (parent==nullptr || parent->child==nullptr)
        return null;
    //do stuff with parent->child->grandchild
}

実際、「各ステップで operator->() をオーバーロードし、これが null の場合は短絡する」と述べていますが、それでさえ (プリプロセッサなしで) 実行できるとは思いませ。オーバーロードされている場合は、null を返すようにすることができますが、operator->()その場合、呼び出しは既に未定義の動作でした。そうでなかったとしても、おそらくすでに戻っていただろう。例外をスローさせることもできますが、パスは上記のコードよりもはるかに複雑だと思います。thisnulloperator->()null

プリプロセッサを使えば何かあるのかもしれませんが、私の最初の考えは醜くて危険です

#define MAYBENULL1(X, Y) (X?(X->Y,nullptr)
#define MAYBENULL2(X, Y, Z) (X&&X->Y?X->Y->Z,nullptr)
#define MAYBENULL3(X, Y, Z, W) (X&&X->Y&&X->Y->Z?X->Y->Z->W,nullptr)

type function(type parent) {return MAYBENULL2(parent, child, grandchild);}

しかし、あなたは誰かがそれを台無しにすることを知っています.

parent* get_next_parent();
type function() {return MAYBENULL1(get_next_parent(), child);}
于 2013-05-01T00:06:29.333 に答える