nullptr是指针类型吗?
nullptr
是C++里预定义的一个变量,它的类型是std::nullptr_t
。
判断一个类型是否是指针类型,可以用std::is_pointer
来判断。
测试std::nullptr_t
是否是指针类型的代码如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
#include <myostream.h>
myostream::ostream mycout(std::cout.rdbuf());
#define watch(...) MYOSTREAM_WATCH(mycout, " = ", "\n", "\n\n", __VA_ARGS__)
int main() {
// 确认nullptr就是std::nullptr_t类型
static_assert(std::is_same_v<decltype(nullptr), std::nullptr_t>, "Never happen");
watch(std::is_pointer_v<std::nullptr_t>,
std::is_member_pointer_v<std::nullptr_t>,
std::is_pointer_v<std::nullptr_t *>
);
return 0;
}
|
输出:
1
2
3
|
std::is_pointer_v<std::nullptr_t> = 0
std::is_member_pointer_v<std::nullptr_t> = 0
std::is_pointer_v<std::nullptr_t *> = 1
|
可见std::nullptr_t
并不是一个指针类型。
只不过我们平时常用nullptr
来赋值给任意指针类型,会给人一种std::nullptr_t
是指针类型的错觉。
从上例中还可以看出std::nullptr_t *
等类型是指针类型,它们是指向std::nullptr_t
的指针类型。
原理
std::nullptr_t
不是指针类型,但是却可以转换成任意指针类型,并且以0为值。
它的一种实现方式如下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
|
#ifdef _LIBCPP_HAS_NO_NULLPTR
_LIBCPP_BEGIN_NAMESPACE_STD
struct _LIBCPP_TEMPLATE_VIS nullptr_t
{
void* __lx;
struct __nat {int __for_bool_;};
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t() : __lx(0) {}
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t(int __nat::*) : __lx(0) {}
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR operator int __nat::*() const {return 0;}
template <class _Tp>
_LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR
operator _Tp* () const {return 0;}
template <class _Tp, class _Up>
_LIBCPP_INLINE_VISIBILITY
operator _Tp _Up::* () const {return 0;}
friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator==(nullptr_t, nullptr_t) {return true;}
friend _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR bool operator!=(nullptr_t, nullptr_t) {return false;}
};
inline _LIBCPP_INLINE_VISIBILITY _LIBCPP_CONSTEXPR nullptr_t __get_nullptr_t() {return nullptr_t(0);}
#define nullptr _VSTD::__get_nullptr_t()
_LIBCPP_END_NAMESPACE_STD
#else // _LIBCPP_HAS_NO_NULLPTR
namespace std
{
typedef decltype(nullptr) nullptr_t;
}
#endif // _LIBCPP_HAS_NO_NULLPTR
|
可见std::nullptr_t
不是指针类型,可能是类似类的类型。
所以当然可以构造出指向std::nullptr_t
的指针类型了,例如std::nullptr_t *
,const std::nullptr_t *
等。
那么std::nullptr_t
是类类型吗?
nullptr是类类型吗?
通过以上源码可以看出std::nullptr_t
通过宏开关设置了两种实现方式,
第一种就是一个普通的struct,是类类型,而第二种似乎是编译器内置的,不是类类型。
所以std::nullptr_t
的类型是依赖实现的,可能是类类型,也可能不是。
判断一个类型是否是类类型,可用std::is_class。测试代码如下:
1
2
3
4
5
6
7
8
|
watch(std::is_class_v<std::nullptr_t>,
std::is_union_v<std::nullptr_t>,
std::is_null_pointer_v<std::nullptr_t>,
std::is_scalar_v<std::nullptr_t>,
std::is_object_v<std::nullptr_t>);
watch(std::is_null_pointer_v<std::nullptr_t>,
std::is_null_pointer_v<void*>);
|
Clang和GCC在 -std=c++17 下默认输出是一样的:
1
2
3
4
5
6
7
8
|
std::is_class_v<std::nullptr_t> = 0
std::is_union_v<std::nullptr_t> = 0
std::is_null_pointer_v<std::nullptr_t> = 1
std::is_scalar_v<std::nullptr_t> = 1
std::is_object_v<std::nullptr_t> = 1
std::is_null_pointer_v<std::nullptr_t> = 1
std::is_null_pointer_v<void*> = 0
|
可见默认情况下std::nullptr_t
也不是类类型,它就是独特的一个类型,可用std::is_null_pointer
来判断。
(当然,如果在Clang下编译时手动添加宏定义_LIBCPP_HAS_NO_NULLPTR,那么std::is_class_v<std::nullptr_t>
就等于true了,它就是普通的类类型了。)
nullptr能被取地址吗?
nullptr是右值临时变量,无法取地址,但是我们可以使用std::nullptr_t
重新定义一个新的"nullptr"左值,然后就可以对其取地址了。代码如下:
1
2
3
4
5
|
// watch(nullptr, &nullptr); // error: cannot take the address of an rvalue of type 'nullptr_t'
std::nullptr_t null1, null2;
watch(null1, &null1);
watch(null2, &null2);
|
输出:
1
2
3
4
5
|
null1 = nullptr
&null1 = 0x16b1d3578
null2 = nullptr
&null2 = 0x16b1d3570
|
如何用C++的方式把"T*"转换成"void*"
假如T
是某种未知的模版类型,如何把T*
的变量转换成void*
类型呢?
用C语言的强制类型转换很容易实现:
1
2
3
4
|
template <typename T>
void* c_cast(T* p) { // 使用C风格的强制类型转换
return (void*)(p);
}
|
那么如何用C++的类型转换方式实现呢?
事实上只用reinterpret_cast或static_cast是不行的,因为它们不能将 cv-qualified pointer 转换成
cv-unqualified pointer。
所以必须首先使用const_cast将cv属性去掉,然后再使用reinterpret_cast或static_cast。如下:
1
2
3
4
5
6
7
|
template <typename T>
void* cpp_cast(T* p) { // 使用C++风格的强制类型转换
// return reinterpret_cast<void*>(p); // error
return reinterpret_cast<void*>(const_cast<std::remove_cv_t<T>*>(p)); // OK
// 或者用static_cast亦可
// return static_cast<void*>(const_cast<std::remove_cv_t<T>*>(p)); // OK
}
|
cv-qualified std::nullptr_t及其指针
经过以上讨论,知道std::nullptr_t不是指针类型,那么如下两个问题就可迎刃而解。
cv-qualified std::nullptr_t 类型的变量能直接赋值给 std::nullptr_t 类型的变量吗?
可以。
1
2
|
const volatile std::nullptr_t p{0};
std::nullptr_t np = p; // OK
|
cv-qualified std::nullptr_t * 类型的变量能直接赋值给 std::nullptr_t * 类型的变量吗?
不可以。需要用const_cast转换后方可赋值。
1
2
3
|
const volatile std::nullptr_t * p{0};
// std::nullptr_t* np = p; // Error
std::nullptr_t* np = const_cast<std::nullptr_t*>(p); // OK
|