2013-04-29 6 views
5

関数言語で使用されているのと同様に、誰もC++でnull伝播演算子を実装していますか?私はいくつかの巧妙なテンプレート解法を考えています。おそらく演算子の振る舞いに似ています - >。C++のNull伝播演算子

グラフのオブジェクトチェーンがfoo->bar->baz(申し訳ありませんが、Demeterの法則)のようになっているとしましょう。これらのどれかがnullである可能性があり、逆参照の前にテストする必要があるとします。その後、コードは突然、はるかに複雑になる:

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

私は「ファクターアウト」は、このようなコンパクトな構文のいくつかの種類ではnullチェックをしたいと思います。今

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

、もちろんそれはdoesnのそのように見えて、ちょうどほぼコンパクトになっていなければならない。必要なのはC++のモナドで、ヌルテストと「継続」を可能にすると思います。マクロやラムダを避けるのはすばらしいことですが、それはおそらく不可能です。 thisがnullの場合、各ステップで演算子 - >()をオーバーロードして短絡することが想像できます。

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

+9

オペレータの使用例を教えてください。 –

+1

あなたはハスケルの 'Maybe'モナドのようなものを意味しますか? – kennytm

+1

私はあなたがあなたの「質問」を肉体的にすべきだと思います。おそらくあなたは「はい、彼らが持っている」と探しているだけではありません。あなたのソリューションに関する何かを尋ねたいなら、それを行います(ただし、[codereview.se]もあります)。 –

答えて

8

デザインで、それはラジカル出発すること、おそらくNull Object Patternを使用して検討するかもしれません。次に、ヌルチェックは一切必要ありません。

+0

私はNull Object Patternを嫌いですが、それは嫌ですが、それは無関係です: '#define MAYBE(X、Y)((&)(){auto ptr = X; return ptr?ptr-> Y:nullptr;})おそらく最高の答えです。 –

1

tldr;私は真剣にC + +がそれを行うことができますか疑問に思う。

残念ながら、私はそれが(プリプロセッサなしで)実行できるとは思いません。しかし、私もその缶はないと思う - 「各ステップで>()と短絡これがnullだった場合はアウト。演算子をオーバーロード」私のコードは、常に実際に

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

のように見える、あなたが言及します(プリプロセッサなしで)実行されます。オーバーロードされた場合、operator->()がnullを返すことがありますが、thisnullの場合、operator->()を呼び出すことは既に定義されていません。たとえそれがなかったとしても、既に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);} 
+2

思いやりのある応答に感謝します。私は演算子 - >をヌルオブジェクトパターンのように組み合わせて、チェーン内のヌルオブジェクトを許可します。しかし、私は最初の場所で厳密なヌルオブジェクトパターンを求めていると思います。 –

+0

これはlambdasで解決できますか? – celtschk

1

C#には2つのタイプのヌル伝播があります。最初はあなたが記述した通りです。fooがnullの場合はnullを返しますが、fooがnullでない場合は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)); 

もちろん、これは要件を満たしていますが、それは不合理で鈍いです。

しかし、「else」の大文字と小文字を区別する必要がないことを意味する、メソッドの呼び出しに使用される2番目のタイプのNull Propagationがあります。ことができますC#で

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

場所::それは相当である。これは、マクロを使用して複製しようとする方がはるかに簡単な機能です

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) 
} 

しかし、これはチェーンと冗長性を与えることはできませんが(おそらく、メソッド呼び出しのヌル伝播を除いて)おそらくほぼすべての可能性のうち、上記の解決方法の問題を正確に解決するわけではありません。

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

編集2 - 同僚と本の話や他のいくつかのオプションを思い付いた:

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

は使用方法:

return Maybe(foo)->bar; 

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

Maybe(foo)->Bar(); 

何それが出て展開することは、有効な(奇妙いえ)

(FOO == nullptr)ですので? nullptr:foo-> Bar(); ?あなたは、変数宣言を置くことができないので、

しかし、:速記、あなたは説明ダックをMooingよう

return Maybe(get_next_parent())->child; 

から守ることができない、と自分で一時的に作成する必要があります。