【问题标题】:Null propagation operator in C++C++ 中的空传播运算符
【发布时间】:2013-04-29 00:43:27
【问题描述】:

有没有人在 C++ 中实现了一个空传播运算符,类似于函数式语言中使用的那个?我正在考虑一些巧妙的模板解决方案,可能类似于 operator-> 的传播行为。

假设我们有一个来自图表的对象链,例如foo->bar->baz(对不起,得墨忒耳定律)。假设其中任何一个都可以为空,并且应该在取消引用之前进行测试。然后代码突然变得复杂了很多:

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

我想用某种紧凑的语法“分解”空检查,如下所示:

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

现在,它当然不必看起来像那样,只要几乎一样紧凑。我想需要的是 C++ 中的 monad,启用空测试和“延续”。最好避免使用宏和 lambda,但这可能是不可能的。我可以想象在每一步都重载 operator->() 并在 this 为空时短路。

但这非常具有侵入性。理想的解决方案是将链中的每个对象包装起来。

【问题讨论】:

  • 你能举一个你的运营商在使用的例子吗?
  • 你的意思是像 Haskell 中的 Maybe monad 吗?
  • 我认为你应该充实你的“问题”;据说你不只是在寻找“是的,他们有”。如果您想询问有关您的解决方案的问题,请这样做(但请记住还有Code Review)。
  • @KennyTM 是的,就像 Maybe monad。也许我的问题需要一定程度的语言“可编程性”,而这在 C++ 中是不存在的。但很好奇是否有人尝试过拼凑它。
  • return foo && foo->bar ? foo->bar->baz : nullptr; 输入太多了吗?或者,如果您有 100 个间接级别怎么办?好吧,那么您可能不应该编写代码。

标签: c++ templates c++11


【解决方案1】:

在您的设计的这个阶段,这可能是一个彻底的偏离,但也许可以考虑使用Null Object Pattern。那么你根本不需要任何空检查。

【讨论】:

  • 虽然我不喜欢 Null 对象模式,但这可能是最好的答案。
【解决方案2】:

tldr;我严重怀疑 C++ 能否做到这一点。

不幸的是,我不相信它可以完成(没有预处理器)。我的代码总是看起来像

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

实际上,您提到“在每个步骤中重载 operator->() 并在 this 为空时短路。”,但我认为即使 也无法做到(没有预处理器)。如果它被重载,你可以让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);}

【讨论】:

  • 感谢您的周到回复。我会将 operator-> 与空对象模式之类的东西结合起来,以允许链中的空对象。但我想这只是首先要求严格的空对象模式。
  • 这可以用 lambdas 解决吗?未经测试:#define MAYBE(X,Y) (([&]() { auto ptr = X; return ptr? ptr->Y : nullptr; })())
【解决方案3】:

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

但这不能被链接起来,并且考虑到几乎所有可能性的冗长(可能除了方法调用 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 描述的那样,需要自己创建一个临时的。

【讨论】:

    猜你喜欢
    • 2015-02-26
    • 2015-12-07
    • 1970-01-01
    • 1970-01-01
    • 2017-11-15
    • 2014-07-31
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多