【问题标题】:C# null coalescing operator equivalent for c++C# null 合并运算符等效于 c++
【发布时间】:2010-12-19 14:48:00
【问题描述】:

C# 空合并运算符是否有 C++ 等价物?我在我的代码中做了太多的空检查。所以一直在寻找一种方法来减少空代码的数量。

【问题讨论】:

  • 在 GCC 中,它是 a ?: b,但它是不可移植的。

标签: c# c++ null null-coalescing-operator


【解决方案1】:

我刚刚发现了这个:The ?? operator aka the Null Coalescing Operator

您还可以在 C/C++ 中使用 ?: 将其作为 GNU 扩展 运营商:

string pageTitle = getTitle() ?: "Default Title";

【讨论】:

  • 链接已损坏并导致 404。
  • 新链接:gcc.gnu.org/onlinedocs/gcc-5.3.0/gcc/… 简而言之,GNU 扩展允许您省略三元组的中间,这会导致条件成为“非零”情况下的返回值
【解决方案2】:

在 C++ 中默认没有办法做到这一点,但你可以写一个:

在 C# 中的 ??运算符定义为

a ?? b === (a != null ? a : b)

所以,C++ 方法看起来像

Coalesce(a, b) // put your own types in, or make a template
{
    return a != null ? a : b;
}

【讨论】:

  • 在此方法中,即使 a 不为空,也会评估 b。如果 b 有副作用,这可能是个问题。
  • @Amnon:确实。考虑:p = Coalesce(p, new int(10));。此外,在 C# 中,右手操作数似乎不能为 NULL(不能在编译时检查,因为 C++ 中没有可为空的类型?)。另一方面:C++ 中是否需要它,以至于你不能只输入a ? a : b;
  • 如果是 C++11 或更高版本,请使用nullptr。无论如何,C/C++ 中没有 null,在某些头文件中只定义了 NULLstackoverflow.com/questions/1282295/what-exactly-is-nullptr
  • @Lunyx:在A??B??C 的情况下,B??C 是第一个?? 的右手操作数。换句话说,它相当于A??(B??C)。此外,没有限制右手运算符不能为空,它只会导致整个事情为空。
  • 糟糕,我的错,你是对的。由于“a”和“b”是局部变量,所以这段代码是线程安全的
【解决方案3】:

使用模板和 C++11 lambda:

template<typename TValue, typename TRhsEvaluator>
TValue coalesce(TValue lhsValue, TRhsEvaluator evaluateRhs) {
     return lhsValue ? lhsValue : evaluateRhs();
}
  • 第一个参数(左侧)只计算一次。
  • 仅当第一个参数为假时才计算第二个参数(右侧)。

请注意,if? 将提供的表达式静态转换为 bool,并且指针有一个内置的 explicit operator bool() const 运算符,它等同于 != nullptr

示例用法:

void * const      nonZeroPtr = reinterpret_cast<void *>(0xF);
void * const otherNonZeroPtr = reinterpret_cast<void *>(0xA);

std::cout << coalesce(nonZeroPtr, [&] () {
    std::cout << "Side-effect. Should never be printed" << std::endl;
    return otherNonZeroPtr;
}) << std::endl;

上面的代码只会将0xf打印到控制台。

右侧需要包裹在 lambda 中 - 我们无法避免该样板文件。实际上,该语言应该提供开箱即用的空合并运算符。

【讨论】:

  • 这是唯一提供与空合并运算符相同语义的答案。不过,这可能很少值得大惊小怪。真的,这个功能只需要在语言中。
【解决方案4】:

只想通过概括模板并添加辅助宏来减少 lambda 样板来扩展 @Samuel Garcia 的答案:

#include <utility>

namespace coalesce_impl
{
    template<typename LHS, typename RHS>
    auto coalesce(LHS lhs, RHS rhs) ->
        typename std::remove_reference<decltype(lhs())>::type&&
    {
        auto&& initialValue = lhs();
        if (initialValue)
            return std::move(initialValue);
        else
            return std::move(rhs());
    }

    template<typename LHS, typename RHS, typename ...RHSs>
    auto coalesce(LHS lhs, RHS rhs, RHSs ...rhss) ->
        typename std::remove_reference<decltype(lhs())>::type&&
    {
        auto&& initialValue = lhs();
        if (initialValue)
            return std::move(initialValue);
        else
            return std::move(coalesce(rhs, rhss...));
    }
}

#define COALESCE(x) (::coalesce_impl::coalesce([&](){ return ( x ); }))
#define OR_ELSE     ); }, [&](){ return (

使用宏,您可以:

int* f();
int* g();
int* h();

int* x = COALESCE( f() OR_ELSE g() OR_ELSE h() );

我希望这会有所帮助。

【讨论】:

    【解决方案5】:

    有一个 GNU GCC 扩展允许在缺少中间操作数的情况下使用 ?: 运算符,请参阅 Conditionals with Omitted Operands

    条件表达式中的中间操作数可以省略。那么如果 第一个操作数非零,它的值是 条件表达式。

    因此,表达式

    x ? : y

    如果不为零,则其值为x;否则,值 y.

    这个例子完全等价于

    x ? x : y

    在这个简单的例子中,省略中间操作数的能力 不是特别有用。当它变得有用时是第一个 操作数确实或可能(如果它是宏参数)包含边 影响。然后在中间重复操作数将执行 副作用两次。省略中间操作数已使用该值 在没有重新计算的不良影响的情况下计算。

    clang 也支持此扩展。但是,在使用扩展之前,您应该检查您正在使用的编译器和代码的可移植性要求。值得注意的是,MSVC C++ 编译器不支持?: 中的省略操作数。

    另请参阅 StackOverflow 相关讨论 here

    【讨论】:

      【解决方案6】:

      这里有两个宏来分别复制?.?? 运算符。如果a 为空,它们都只安全地评估操作数一次并完全跳过评估b。他们还使用 lambda 语法来限制这些评估的范围,并确保不会搞砸:

      #define COACALL(a, b) ([&](){ auto val = (a); if (val) (val->b); }());
      #define COALESCE(a, b) ([&](){ auto val = (a); return ((val) == NULL ? (b) : (val)); }()) 
      

      这样使用:

      COACALL( goodPtr, sayHello() );
      COACALL( nullPtr, sayHello() );
      
      COALESCE( nullPtr, goodPtr )->sayHello();
      COALESCE( nullPtr, COALESCE( nullPtr, goodPtr ) )->sayHello();
      
      COACALL( COALESCE( nullPtr, goodPtr ), sayHello() );
      

      【讨论】:

      • 这个 COALESCE 实现很简单,并且避免了其他答案的缺点。应该得到更高的评价
      【解决方案7】:

      这个怎么样?

      #define IFNULL(a,b) ((a) == null ? (b) : (a))
      

      【讨论】:

      • 提防 IFNULL(a++, b) 是的,我知道如果您认为 a 可能为空,那么无论如何您都会想提防这一点。 IFNULL(SomeClass.DoSomethingReallyLong(), "") 也会导致问题。
      • 好点——适合我懒惰。使用模板方法绝对是这里的方法,因为它避免了副作用并且将具有相同的性能(或更好)。我现在要在@McKay 的解决方案上添加+1。 :-)
      • 函数模板不一定具有相同或更好的性能。这种“短路”(b 不会被评估,除非a 为空),而函数的参数总是被评估。
      • 也是一个好点。但是,我认为最常见的用例(至少在我使用 ?? 运算符的 C# 中)是 a 比 b 更昂贵(例如数据库调用、XML 解析)。通常 b 是一个常数,如 0 或 ""。所以我怀疑模板大部分时间都会胜出。另外,除非您知道宏是如何实现的,否则双重执行对于宏的调用者来说并不明显,而函数调用语义是众所周知的,因此任何使用昂贵 b 的人都会知道跳过该函数并手动创建一个临时变量。有趣的是,我反对我自己的答案... :-)
      • 你可以用 lambda 包装,然后 a 将永远不会被评估两次。 #define IFNULL(a, b) ([&amp;](){ auto val = (a); return ((val) == NULL ? (b) : (val)); }())
      【解决方案8】:

      只是添加到提到?: 运算符(“猫王运算符”)的答案:我有时将辅助函数与此运算符一起使用,以获取指针或std::optional 或类似类型的基础值“ wrap" 一个值并进行布尔转换以指示值的存在。例如:

      template <typename T>
      constexpr T coalesce (std::optional<T> opt) {
          return *opt;
      }
      template <typename T>
      constexpr T coalesce (T fallback) {
          return fallback;
      }
      
      std::optional<int> opt1{5};
      std::optional<int> opt2;
      int val1 = coalesce(opt1 ?: 0);
      int val2 = coalesce(opt2 ?: 0);
      

      唯一的缺点是它必须小心使用,并且不会给你一个正确使用的静态检查。例如。您可以在没有?: fallback 的情况下执行coalesce(opt2),这与在不首先检查它是否包含任何内容的情况下执行*opt2 相同。所以coalesce 这个名字有点误导,但在我看来,如果使用得当,它看起来不言自明(而且非常整洁)。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2019-07-06
        • 2012-11-28
        • 2011-06-26
        • 2011-08-09
        • 1970-01-01
        • 2019-02-11
        • 2012-12-29
        • 2010-12-25
        相关资源
        最近更新 更多