是否有人在C ++中实现了一个空传播运算符,类似于函数式语言中使用的运算符?我正在考虑一些聪明的模板解决方案,可能类似于运营商的传播行为 - >。
假设我们有一个图形对象链,如foo->bar->baz
(对不起,德米特法则)。假设其中任何一个都可以为null,并且应在解除引用之前进行测试。然后代码突然变得复杂得多:
if( !foo )
return nullptr;
if( !foo->bar )
return nullptr;
return foo->bar->baz;
我想用某种紧凑的语法“分解”空检查,如下所示:
foo?->bar?->baz // imaginary null-propagation syntax
现在,它当然不一定非常紧凑。我想我们需要的是C ++中的monad,启用null测试和'continuation'。避免使用宏和lambda会很棒,但这可能是不可能的。我可以想象在每一步都重载operator->()并在this
为空时短路。
但这非常具有侵扰性。理想的解决方案是将每个对象包装在链中。
答案 0 :(得分:8)
在您的设计的这个阶段,它可能是一个彻底的离开,但也许考虑使用Null Object Pattern。那么你根本不需要任何空检查。
答案 1 :(得分:1)
<强> tldr;我严重怀疑C ++可以做到这一点。
不幸的是,我不相信它可以做到(没有预处理器)。我的代码总是看起来像
type function(type parent) {
if (parent==nullptr || parent->child==nullptr)
return null;
//do stuff with parent->child->grandchild
}
事实上,你在每一步都提到“重载operator-&gt;()并且如果这是空的话就会短路。”,但我不认为即使可以完成(没有预处理器)。如果它被重载,您可以使operator->()
返回null,但如果this
为null
,则调用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 :(得分:1)
C#中有两种类型的空传播。第一个是您所描述的,如果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));
当然,虽然这符合要求,但这是荒谬和迟钝的。
然而,值得注意的是,有第二种类型的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)
}
但是这不能被链接,并且考虑到几乎所有可能性的冗长(可能除了方法调用空传播之外),你不会完全解决任何上述解决方案的任何问题。
编辑 - 使模板化的答案略微。
编辑2 - 与同事讨论这个问题并提出了其他一些选择:
#define Maybe(X) (X==nullptr) ? nullptr : X
用法:
return Maybe(foo)->bar;
有趣的是,这也适用于调用方法案例。
Maybe(foo)->Bar();
因为它扩展到的是有效的(虽然很奇怪)
(foo == nullptr)? nullptr:foo-&gt; Bar();
但是,因为你不能把变量声明放在?:简写中,所以你无法保护
return Maybe(get_next_parent())->child;
像Mooing Duck所描述的那样,并且需要自己创造一个暂时的。