我正在处理一个相当统一的供应商提供的 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
s 可以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
所以:
-
对于 C,
NULL
是(void *)0
; -
对于 clang++ 来说
NULL
和nullptr
是同一件事,而对于 GNU 来说可能不是……
5
最佳答案
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
或 为零值整数文字(例如0
或0L
)。哪一个是实现定义的,但由于只有后者在 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 *)0
C 有什么问题?
– -
中有相当多的原理。但它仍然遭受我在 C++ 答案中提到的同样的怪异:在 C 中,只有特定的整型常量表达式
0
或特定的整型常量表达式的值强制0
转换为void*
空指针常量,才能安全地转换为空指针值。与 C++ 相比,隐式转换为指针类型始终是可能的,但结果可能无法保证。
–
|
NULL
是已定义的实现,并且可能被定义为0
(针对遗留情况)或nullptr
(更好)。但也许你nullptr
本来就是有意为之?换句话说,你使用的动机NULL
到底是什么?–
handlepath()
——是一个 C 函数,并且所有供应商提供的示例都使用NULL
。–
constexpr auto ø = nullptr;
现在您可以使用ø
。–
NULL
弃用。它仅用于与 C 的向后兼容性。如果您不打算针对 C 进行编译,则只需使用nullptr
;就无意的整数转换而言,它更安全。–
NULL
只需要扩展为空指针常量,该值等于零 -未指定类型。实际上,一些实现将其定义为0
或0L
或类似,因此它具有整数类型,但其他实现将其定义为具有指针类型(例如,它扩展为(void *)0
)。在 C++ 中,nullptr
保证具有类型std::nullptr_t
,可以隐式转换为指针。这对模板参数包很重要,因为它们不进行任何隐式转换(例如将转换0
为指针)。–
|