C# 中有两种类型的空值传播。第一个是如您所描述的,如果foo
为空,则返回空,但如果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));
当然,虽然这符合要求,但它是荒谬和迟钝的。
然而,值得注意的是,还有第二种类型的 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 描述的那样,需要自己创建一个临时的。