在C里面,由于处处都要使用指针,所以导致NULL遍布各地。我们先来看C99是怎么定义NULL的:
可见,在C99里面,NULL可以被定义为0或者0L(32位和64位的区别),或者直接就是由0或者0L转成的成void*。 接下来我们来看下C++ 14(N4296)中所定义的null pointer。NULL can be defined as any null pointer constant. Thus existing code can retain definitions of
NULL as 0 or 0L, but an implementation may also choose to define it as (void*)0. This latter
form of definition is convenient on architectures where sizeof(void*) does not equal the
size of any integer type. It has never been wise to use NULL in place of an arbitrary pointer as a
function argument, however, since pointers to different types need not be the same size. Thelibrary avoids this problem by providing special macros for the arguments to signal, the one
library function that might see a null function pointer.
第一句话就表明了,在C++中,一个指向空的指针要么是一个字面值整形,要么是一个std::nullptr_t 我们再来看VS 2015 中所定义的NULL,就是一个0
A null pointer constant is an integer literal (2.13.2) with value zero or a prvalue of type std::nullptr_t.
A null pointer constant can be converted to a pointer type; the result is the null pointer value of that type
and is distinguishable from every other value of object pointer or function pointer type. Such a conversion
is called a null pointer conversion. Two null pointer values of the same type shall compare equal. The
conversion of a null pointer constant to a pointer to cv-qualified type is a single conversion, and not the
sequence of a pointer conversion followed by a qualification conversion (4.4). A null pointer constant of
integral type can be converted to a prvalue of type std::nullptr_t. [ Note: The resulting prvalue is not a
null pointer value. —end note ]
A prvalue of type “pointer to cv T,” where T is an object type, can be converted to a prvalue of type “pointer
to cv void”. The result of converting a non-null pointer value of a pointer to object type to a “pointer to
cv void” represents the address of the same byte in memory as the original pointer value. The null pointer
value is converted to the null pointer value of the destination type.
A prvalue of type “pointer to cv D”, where D is a class type, can be converted to a prvalue of type “pointer
to cv B”, where B is a base class (Clause 10) of D. If B is an inaccessible (Clause 11) or ambiguous (10.2)
base class of D, a program that necessitates this conversion is ill-formed. The result of the conversion is a
pointer to the base class subobject of the derived class object. The null pointer value is converted to the
null pointer value of the destination type.
#ifndef NULL
#ifdef __cplusplus
#define NULL 0
#else
#define NULL ((void *)0)
#endif
#endif
用nullptr解决C++中NULL所不能解决的问题
前面我们说了,C++中的NULL,其实就是一个0,这会导致很多问题,比如我们可以写一个函数重载:
#include <iostream>
#include <algorithm>
#include <memory>
void fun(int)
{
std::cout << "fuck1" << std::endl;
}
void fun(void *)
{
std::cout << "fuck2" << std::endl;
}
int main(int argc, char *argv[])
{
fun(NULL);
system("pause");
return 0;
}
一般来说,我们传进去一个NULL,一般想的是要传一个指针,可是在上面的程序中,我们却调用的是int的版本。
但是当我们传的是nullptr时:
int main(int argc, char *argv[])
{
fun(nullptr);
system("pause");
return 0;
}
这个时候调用的是第二个版本了,符合我们的设想,这是因为C++规定nullptr可以转为指针类型。而且是cv void *
再来一个例子,也就是我们最常见的模板匹配问题了:
struct Fuck
{
Fuck(char *){ }
};
int main(int argc, char *argv[])
{
auto p = std::make_shared<Fuck>(NULL);
throwing();
system("pause");
return 0;
}
这个代码会报错,至于为什么,我们先来分析一下make_shared的模板:
template<class _Ty,
class... _Types> inline
shared_ptr<_Ty> make_shared(_Types&&... _Args)
{ // make a shared_ptr
_Ref_count_obj<_Ty> *_Rx =
new _Ref_count_obj<_Ty>(_STD forward<_Types>(_Args)...);
shared_ptr<_Ty> _Ret;
_Ret._Resetp0(_Rx->_Getptr(), _Rx);
return (_Ret);
}
// TEMPLATE CLASS _Ref_count_obj
template<class _Ty>
class _Ref_count_obj
: public _Ref_count_base
{ // handle reference counting for object in control block, no allocator
public:
template<class... _Types>
_Ref_count_obj(_Types&&... _Args)
: _Ref_count_base()
{ // construct from argument list
::new ((void *)&_Storage) _Ty(_STD forward<_Types>(_Args)...);
}
_Ty *_Getptr() const
{ // get pointer
return ((_Ty *)&_Storage);
}
private:
virtual void _Destroy() _NOEXCEPT
{ // destroy managed resource
_Getptr()->~_Ty();
}
virtual void _Delete_this() _NOEXCEPT
{ // destroy self
delete this;
}
typename aligned_union<1, _Ty>::type _Storage;
};
这里多说几句,make_shared的操作是先给_Ref_count_obj<_Ty>类型分配一块内存,然后再placement new,回想一下我们平常使用shared_ptr的时候,都是shared_ptr<T> foo(new T(arg...))这样用的,但是其实用make_shared创建shared_ptr的方法更为高效,因为我们从模板中可以看到shared_ptr的占用空间其实是要比T要大的(为了保存引用计数的东西)。如果我们使用shared_ptr<T> foo(new T(arg...))来构造shared_ptr,那么要先给T分配内存并构造T,然后在分配ref_count的内存,但是如果使用make_shared,那么就会直接给T和ref_count一起分配内存,然后再通过C++11的完美转发把T的构造函数传给make_shared。
好现在回到我们这篇博客的主题,为什么传一个NULL会报错呢?这是因为由于C++的NULL就是一个字面值常量0,所以传进去时,会被forward推断成int &&,int &&与char *当然不是一个东西,就会报错。
这个时候我们就必须使用nullptr了,nullptr可以转换成void *,然后再隐式转换成char *
auto p = std::make_shared<Fuck>(nullptr);