C++11 – nullptr != NULL

C++11 standartlarıyla beraber C++ programlama dili yavaş yavaş daha deterministik olmaya başladı. Yani belirsiz durumlar, kullanım hataları dilin standartlarının desteğiyle en aza çekiliyor. Bu kapsamda nullptr de uzun zamandır bilinen bir sorunu çözüyor.

#include <iostream>
#include <memory>


using namespace std;



void func(int x)
{
cout << "int" << endl;
}

void func(char *y)
{
cout << "pointer" << endl;
}

int main() {
func(0); // int overload
func((char *)0); // char * overload
func(nullptr); // char* overload
func(NULL); // main.cpp:23:14: error: call of overloaded ‘func(NULL)’ is ambiguous (compiler: g++5)
}

Üstteki hata aslında derleyiciden derleyiciye farklı olabilir, örneğin visual studio da ‘int’ olarak yorumlanabilir. Kısacası non-deterministic bir durum sözkonusu, derleyicinden derleyiciye farkediyor.
Aslında sorun C++ programlama dilinin iki temel özelliğinden kaynaklanıyor; Function Overloading ve Otomatik Tür dönüşümü. Şimdi bir de NULL isminin nasıl tanımlandığına bakalım:

#if defined (_STDDEF_H) || defined (__need_NULL)
#undef NULL /* in case <stdio.h> has defined it. */
#ifdef __GNUG__
#define NULL __null
#else /* G++ */
#ifndef __cplusplus
#define NULL ((void *)0)
#else /* C++ */
#define NULL 0
#endif /* C++ */
#endif /* G++ */
#endif /* NULL not defined and <stddef.h> or need NULL. */
#undef __need_NULL

Görüldüğü üzere derleyiciniz cplusplus derleyicisi ise NULL ismi 0 olarak tanımlanmış. İşte tam da burda sıkıntı ortaya çıkıyor.

Derleyici ‘func(NULL)’ dediğinizde hangi fonksiyonu çağıracağını bilmiyor(g++5 için) ya da kullandığınız başka bir derleyici de farklı bir davranış gözlemleyebilirsiniz integer veya char * function overloadlarından biri çağırılabilir. Hülasası başta da belirttiğim gibi C++11 bu tarz non-deterministic konulara da el atıyor ve bu meseleyi nullptr ile çözüyor. Artık NULL yazdığınız her yere nullptr yazarak daha güvenli programlama yapmış olabilirsiniz özellikle cross-platform yazılım geliştiyorsanız.

nullptr’ nin aslında türü std::nullptr_t ve bir prvalue’ dur. Dolayısıyla herhangi bir şekilde deference edilemez.

Bunun yanı sıra nullptr meta programmingte perfect forwardinge de olanak sağlamaktadır. Şöyle ki:

#include <iostream>
#include <memory>


using namespace std;


template<class F, class A>
void foo(F f, A a)
{
f(a);
}


void func(char *y)
{
cout << "pointer" << endl;
}

int main() {
func(0); // int overload
func((char *)0); // void * overload
func(nullptr); // void * overload

//foo(func, NULL); //error: invalid conversion from ‘long int’ to ‘char*’ [-fpermissive]
foo(func, nullptr);
}

foo şablon fonksiyonunun ikinci parametresine NULL verirsek derleyici f fonksiyonunu integer bir değerle çağırmaya çalışacak. Çünkü şablon parametresi olarak NULL ‘ ın türü integer tespit edildi. nullptr ile çağırdığımızda ise türü std::nullptr_t olarak tespit edileceğinden sorunsuzca ‘f’ veya ‘func’ fonksiyonu sorunsuzca çağırılacaktır.

Aslında nullptr nin implementasyonu bir const class’dır. Merak edenler için standart taslakta implementasyonu şu şekildedir:

const // this is a const object...
class {
public:
template<class T> // convertible to any type
operator T*() const // of null non-member
{ return 0; } // pointer...
template<class C, class T> // or any type of null
operator T C::*() const // member pointer...
{ return 0; }
private:
void operator&() const; // whose address can't be taken
} nullptr = {};