我正在处理一个相当统一的供应商提供的 API,并且希望以统一的方式检查和处理任何故障。为此,我编写了以下包装器:

template <typename Func, typename... Args>
auto awrap(Func &func, Args&&... args)
{
    auto code = func(args...);

    if (code >= 0)
        return code;

    ... handle the error ...
};
...
awrap(handlepath, handle, path, NULL, 0, coll, NULL);

上面的代码可以通过 clang 编译,但是 g++13 和 Microsoft VC++ 都对两个 NULL 参数提出抱怨:

... error: invalid conversion from 'int' to 'const char*' [-fpermissive]
   87 |                 auto code = func(args...

用 替换两个NULLs 可以nullptr解决问题,但是这有什么关系呢?

很可能,预处理器NULL会将 转换为0x0,甚至0在某个地方,但原始调用从未引起“注意”。在以下情况下,使用 NULL 非常合适:

handlepath(handle, path, NULL, 0, coll, NULL);

为什么在包装器中使用时会出现问题(对于某些编译器而言)?


更新/usr/include/sys/_null.h我的 FreeBSD 系统上有以下代码:

#ifndef NULL

#if !defined(__cplusplus)
#define NULL    ((void *)0)
#else
#if __cplusplus >= 201103L
#define NULL    nullptr
#elif defined(__GNUG__) && defined(__GNUC__) && __GNUC__ >= 4
#define NULL    __null
#else
#if defined(__LP64__)
#define NULL    (0L)
#else
#define NULL    0
#endif  /* __LP64__ */
#endif  /* __GNUG__ */
#endif  /* !__cplusplus */

#endif

所以:

  1. 对于 C,NULL(void *)0

  2. 对于 clang++ 来说NULLnullptr是同一件事,而对于 GNU 来说可能不是……

5

  • 2
    NULL是已定义的实现,并且可能被定义为0(针对遗留情况)或nullptr(更好)。但也许你nullptr本来就是有意为之?换句话说,你使用的动机NULL到底是什么?


    – 

  • 被包装的函数——这个handlepath()——是一个 C 函数,并且所有供应商提供的示例都使用NULL


    – 


  • constexpr auto ø = nullptr;现在您可以使用ø


    – 

  • 2
    如果使用现代 C++,请考虑NULL弃用。它仅用于与 C 的向后兼容性。如果您不打算针对 C 进行编译,则只需使用nullptr;就无意的整数转换而言,它更安全。


    – 

  • 1
    在 C 和(参考 C)C++ 中,NULL只需要扩展为空指针常量,该值等于零 -未指定类型。实际上,一些实现将其定义为00L或类似,因此它具有整数类型,但其他实现将其定义为具有指针类型(例如,它扩展为(void *)0)。在 C++ 中,nullptr保证具有类型std::nullptr_t,可以隐式转换为指针。这对模板参数包很重要,因为它们不进行任何隐式转换(例如将转换0为指针)。


    – 



最佳答案
2

在许多实现中,NULL仅仅是#define整数字面量的 0,例如:

#define NULL 0

您可以直接将文字 0分配给任何指针,这就是NULL直接传递给目标函数可以正常工作的原因。

由于您要传递NULL给模板参数,因此该参数将被推断为类型int,正如错误消息所示。

在此调用中:

awrap(handlepath, handle, path, NULL, 0, coll, NULL);

awrap()将解析为如下内容:

auto awrap(decltype(handlepath) &func, decltype(handle) arg1, decltype(path) arg2,
    int arg3, int arg4, decltype(coll) arg5, int arg6)
 // ^^^^^^^^                                 ^^^^^^^^
{
    auto code = func(arg1, arg2, arg3, arg4, arg5, arg6);
    //                    error: ^^^^       error: ^^^^
    ...
};

要将int变量分配给指针,您需要进行显式类型转换,但您并未使用它。

nullptr是 类型nullptr_t,也可以直接分配给任何指针。 只有一个可能的值nullptr_t。因此,当传递nullptr给模板参数时,该参数将被推断为 类型nullptr_t。并且编译器知道如何将 分配nullptr_t给指针。

在此调用中:

awrap(handlepath, handle, path, nullptr, 0, coll, nullptr);

awrap()将解析为如下内容:

auto awrap(decltype(handlepath) &func, decltype(handle) arg1, decltype(path) arg2,
    nullptr_t arg3, int arg4, decltype(coll) arg5, nullptr_t arg6)
 // ^^^^^^^^^^^^^^                                 ^^^^^^^^^^^^^^
{
    auto code = func(arg1, arg2, arg3, arg4, arg5, arg6);
    //                       OK: ^^^^          OK: ^^^^
    ...
};

因此,当调用模板时,您应该使用nullptr

但是,如果您想使用,NULL那么您必须将其类型转换为(char*)NULL(或等效类型),或参数期望的任何其他指针类型,例如:

awrap(handlepath, handle, path, (char*)NULL, 0, coll, (char*)NULL);

6

  • 仍然不明白,为什么在包装之前传递 NULL 是可以的。而且,为什么clang这里甚至没有警告(更不用说错误)…


    – 

  • 看到“在许多实现中”后,我投了赞成票。感谢您对此进行了适当的限定。


    – 


  • @MikhailT. 正如我所解释的,将文字 0传递给指针是可行的。因此,NULL直接传递给原始函数的指针参数是可行的。但是,您的包装器增加了一个间接级别,您不再将文字直接 0传递给原始函数的指针参数。您现在将其传递NULL给(推导的)int包装器参数,然后将其传递int给原始函数的指针参数。如果没有类型转换,这将无法工作。


    – 


  • (char*)NULL(又名reinterpret_cast<char*>(0) ”:这不正确。(char*)NULL将解析为static_cast<char*>(NULL)使用隐式转换。事实上,从reinterpret_cast<char*>(0)技术上讲,不能保证按预期运行。不需要reinterpret_cast<char*>(0)产生空指针值。这也是为什么(char*)arg函数内部会出现问题并且转换太晚的原因。nullptr这确实是这里唯一干净的解决方案。


    – 


  • 1
    @MikhailT。这基本上可以归结为char* foo = 0;和之间的区别int i = 0; char* foo = i;。由于NULL只是一个扩展为文字的宏0,前者是模板包装器之前所拥有的,而后者是模板包装器之后所拥有的。


    – 


NULL是一个宏,可以定义为nullptr或 为零值整数文字(例如00L)。哪一个是实现定义的,但由于只有后者在 C 和 C++11 之前有效,因此看到后者的机会很大。

nullptr是 的对象nullptr_t。任何此类型的表达式始终是所谓的空指针常量,这意味着它可以隐式转换为任何指针类型的空指针值。

然而,零值整数表达式并不总是指针常量。具体来说,只有空值整数文字才是可以转换为指针类型的空指针常量。

因此,如果直接传递给期望正常运行的0函数,则文字是空指针常量,并且可以隐式转换为指针类型,从而产生空指针值。char*0

但是您将文字awrap作为整数类型传递给 (通过推导)。当您使用参数时,func(args...)它仍然是一个值为零的整数表达式,但它不是整数文字。因此,参数不是指针常量,不能隐式转换为char*

因此,您不能通过0包装器传递指针参数,因为您必须准确命名转换应应用的位置的文字。并且是否可以使用NULL是实现定义的。如果NULL恰好定义为,它可以工作nullptr,但如果它是零值整数文字则不行。

这是一个很好的例子,说明为什么只nullptr应使用。零值整数文字的隐式转换行为仅因从 C 继承的历史原因而存在。如果该语言是今天设计的,我怀疑是否有人会在转换中添加如此奇怪的特殊情况。通常,可能的转换由表达式的类型和值类别决定。这是值和语法结构影响转换行为的唯一特殊情况。

4

  • 它还使 C++ 与 C 又向前迈进了一步……


    – 

  • 1
    @MikhailT。C23 现在也有了nullptr。我认为我的最后一段也应该适用于 C。


    – 

  • (void *)0C 有什么问题?


    – 

  • 中有相当多的原理。但它仍然遭受我在 C++ 答案中提到的同样的怪异:在 C 中,只有特定的整型常量表达式0或特定的整型常量表达式的值强制0转换为void*空指针常量,才能安全地转换为空指针值。与 C++ 相比,隐式转换为指针类型始终是可能的,但结果可能无法保证。


    –