我正在处理一个相当统一的供应商提供的 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...
用
NULL
替换两个 nullptr
可以解决问题,但为什么这很重要?
很可能,预处理器将
NULL
变成 0x0
甚至 0
,但最初的调用从未引起“注意”。使用 NULL 非常适合:
handlepath(handle, path, NULL, 0, coll, NULL);
当在包装器中使用时,为什么它(对于某些编译器)是一个问题?
更新:我的 FreeBSD 系统上的
/usr/include/sys/_null.h
有以下代码:
#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
所以:
对于 C,
NULL
是 (void *)0
;
对于 clang++ 来说
NULL
和 nullptr
是同一件事,而对于 GNU 来说可能不是......
在许多实现中,
NULL
只是#define
,表示整数文字0
,例如:
#define NULL 0
您可以直接将 literal
0
分配给任何指针,这就是为什么将 NULL
直接传递给目标函数效果很好。
由于您将
NULL
传递给模板参数,因此该参数将被推导为类型 int
,如错误消息所示。 要将 int
变量分配给指针,您需要显式类型转换。
而
nullptr
是nullptr_t
类型,也可以直接赋值给任何指针。 nullptr_t
只有 1 个可能的值。因此,当将 nullptr
传递给模板参数时,该参数将被推导为类型 nullptr_t
。并且编译器知道如何将 nullptr_t
分配给指针。
因此,在调用模板时,您应该使用
nullptr
,但是如果您想使用NULL
,那么您必须将其类型转换为(char*)NULL
(或等效项),或者参数的任何其他指针类型期待。
NULL
是一个宏,可以定义为 nullptr
或零值整数文字(例如 0
或 0L
)。哪一个是实现定义的,但因为只有后一种选择在 C 和 C++11 之前版本中有效,所以看到后者的机会很大。
nullptr
是 nullptr_t
的对象。这种类型的任何表达式始终是所谓的“空指针常量”,这意味着它可以隐式转换为任何指针类型的空指针值。
然而,零值整数表达式并不总是空指针常量。具体来说,only空值整数literals是可以转换为指针类型的空指针常量。
因此,如果将 0
直接传递给期望 char*
0
是一个空指针常量,可以隐式转换为指针类型,从而产生空指针值.
但是您将文字作为整数类型传递给
awrap
(通过推导)。当您在
func(args...)
中使用参数时,它仍然是一个值为零的整数表达式,但它不是一个 整数文字
。因此,参数不是
空指针常量,并且不能隐式转换为
char*
。
因此,您无法通过包装器传递指针参数 0
,因为您必须准确命名应该应用转换的文字。是否可以使用
NULL
是由实现定义的。如果 NULL
恰好被定义为
nullptr
,则它有效,但如果它是零值整数文字,则无效。
这是一个很好的例子,说明为什么只应该使用
nullptr
。零值整数文字的隐式转换行为仅因从 C 继承的历史原因而存在。如果该语言是今天设计的,我怀疑有人会在转换中添加如此奇怪的特殊情况。通常,可能的转换由表达式的类型和值类别确定。这是值和语法结构影响转换行为的唯一特殊情况。