Apa itu nullptr?

570

Kami sekarang memiliki C ++ 11 dengan banyak fitur baru. Yang menarik dan membingungkan (setidaknya bagi saya) adalah yang baru nullptr.

Yah, tidak perlu lagi untuk makro jahat NULL.

int* x = nullptr;
myclass* obj = nullptr;

Tetap saja, saya tidak mengerti cara nullptrkerjanya. Sebagai contoh, artikel Wikipedia mengatakan:

C ++ 11 memperbaiki ini dengan memperkenalkan kata kunci baru untuk dijadikan sebagai konstanta penunjuk nol yang dibedakan: nullptr. Ini adalah tipe nullptr_t , yang secara implisit dapat dikonversi dan sebanding dengan tipe penunjuk atau tipe penunjuk-ke-anggota. Ini tidak secara implisit dapat dikonversi atau sebanding dengan tipe integral, kecuali untuk bool.

Bagaimana kata kunci dan turunan tipe?

Juga, apakah Anda memiliki contoh lain (di samping Wikipedia) di mana nullptrlebih baik dari yang lama 0?

AraK
sumber
23
fakta terkait: nullptrjuga digunakan untuk mewakili referensi nol untuk handle yang dikelola di C ++ / CLI.
Mehrdad Afshari
3
Ketika menggunakan Visual C ++, ingatlah bahwa jika Anda menggunakan nullptr dengan kode C / C ++ asli dan kemudian kompilasi dengan opsi kompiler / clr, kompiler tidak dapat menentukan apakah nullptr menunjukkan nilai pointer nol asli atau yang dikelola. Untuk membuat maksud Anda jelas ke kompiler, gunakan nullptr untuk menentukan nilai yang dikelola atau __nullptr untuk menentukan nilai asli. Microsoft telah menerapkan ini sebagai ekstensi komponen.
cseder
6
Apakah nullptr_tdijamin hanya memiliki satu anggota nullptr,? Jadi, jika suatu fungsi dikembalikan nullptr_t, maka kompiler sudah tahu nilai mana yang akan dikembalikan, terlepas dari isi fungsinya?
Aaron McDaid
8
@AaronMcDaid std::nullptr_tdapat dipakai, tetapi semua instance akan identik dengan nullptrkarena jenisnya didefinisikan sebagai typedef decltype(nullptr) nullptr_t. Saya percaya alasan utama tipe ini ada adalah agar fungsi dapat kelebihan beban untuk ditangkap nullptr, jika perlu. Lihat di sini untuk contoh.
Justin Time - Pasang kembali Monica
5
0 pernah ada pointer nol, null pointer adalah pointer yang dapat mendapatkan oleh pengecoran nol literal jenis pointer, dan tidak menunjuk ke setiap objek yang ada dengan definisi.
Swift - Friday Pie

Jawaban:

403

Bagaimana kata kunci dan turunan tipe?

Ini tidak mengejutkan. Kedua truedan falseadalah kata kunci dan sebagai literal mereka memiliki tipe ( bool). nullptradalah pointer tipe literalstd::nullptr_t , dan ini merupakan nilai awal (Anda tidak dapat menggunakan alamatnya &).

  • 4.10tentang konversi penunjuk mengatakan bahwa nilai jenis std::nullptr_tadalah konstanta penunjuk nol, dan konstanta penunjuk nol integral dapat dikonversi menjadi std::nullptr_t. Arah sebaliknya tidak diizinkan. Ini memungkinkan overloading fungsi untuk pointer dan integer, dan meneruskan nullptruntuk memilih versi pointer. Lulus NULLatau 0akan bingung memilih intversi.

  • Sebuah gips nullptr_tuntuk tipe integral membutuhkan a reinterpret_cast, dan memiliki semantik yang sama dengan gips (void*)0untuk tipe integral (implementasi pemetaan didefinisikan). A reinterpret_casttidak dapat dikonversi nullptr_tke tipe pointer apa pun. Andalkan konversi implisit jika memungkinkan atau gunakan static_cast.

  • Standar mengharuskan sizeof(nullptr_t)be sizeof(void*).

Johannes Schaub - litb
sumber
Oh, setelah melihat, menurut saya operator kondisional tidak dapat mengkonversi 0 ke nullptr dalam kasus seperti cond ? nullptr : 0;. Dihapus dari jawaban saya.
Johannes Schaub - litb
88
Catatan yang NULLbahkan tidak dijamin 0. Bisa jadi 0L, dalam hal ini panggilan ke void f(int); void f(char *);akan ambigu. nullptrakan selalu mendukung versi penunjuk, dan tidak pernah memanggil yang intsatu. Perhatikan juga bahwa nullptr dapat dikonversi ke bool(konsep mengatakan bahwa di 4.12).
Johannes Schaub - litb
@ litb: jadi mengenai f (int) dan f (batal *) - akankah f (0) masih ambigu?
Steve Folly
27
@Steve, tidak ada yang akan memanggil intversi. Tapi f(0L)ambigu, karena long -> intjuga sebagai long -> void*adalah keduanya sama-sama mahal. Jadi jika NULL ada 0Ldi kompiler Anda, maka panggilan f(NULL)akan ambigu mengingat dua fungsi tersebut. Tidak demikian halnya dengan nullptrtentu saja.
Johannes Schaub - litb
2
@VenS Ini tidak boleh didefinisikan seperti (void*)0dalam C ++. Tapi itu dapat didefinisikan sebagai sembarang pointer nol konstan, yang merupakan konstanta integral dengan nilai 0 dan nullptrmemenuhi. Jadi, pasti tidak akan tetapi bisa . (Kamu lupa ping saya btw ..)
Deduplicator
60

Dari nullptr: Tipe-aman dan Clear-Cut Null Pointer :

Kata kunci nullptr C ++ 09 nullptr yang baru menetapkan konstanta nilai ulang yang berfungsi sebagai universal null pointer literal, menggantikan buggy dan literal 0 yang diketik dengan lemah dan makro NULL yang terkenal. nullptr dengan demikian mengakhiri lebih dari 30 tahun rasa malu, ambiguitas, dan bug. Bagian berikut ini menyajikan fasilitas nullptr dan menunjukkan bagaimana ia dapat memperbaiki penyakit NULL dan 0.

Referensi lain:

nik
sumber
17
C ++ 09? Bukankah itu disebut sebagai C ++ 0x sebelum Agustus 2011?
Michael Dorst
2
@anthropomorphic Nah itulah tujuannya. C ++ 0x digunakan saat masih dalam proses, karena tidak diketahui apakah akan selesai 2008 atau 2009. Perhatikan bahwa itu sebenarnya menjadi C ++ 0B yang berarti C ++ 11. Lihat stroustrup.com/C++11FAQ.html
mxmlnkn
45

Mengapa nullptr di C ++ 11? Apa itu? Mengapa NULL tidak cukup?

Pakar C ++ Alex Allain mengatakannya dengan sempurna di sini (penekanan saya ditambahkan dengan huruf tebal):

... bayangkan Anda memiliki dua deklarasi fungsi berikut:

void func(int n); 
void func(char *s);

func( NULL ); // guess which function gets called?

Meskipun sepertinya fungsi kedua akan dipanggil - Anda, setelah semua, melewati apa yang tampaknya menjadi pointer - itu benar-benar fungsi pertama yang akan dipanggil! Masalahnya adalah bahwa karena NULL adalah 0, dan 0 adalah bilangan bulat, versi pertama dari func akan dipanggil sebagai gantinya. Ini adalah jenis hal yang, ya, tidak terjadi setiap saat, tetapi ketika itu terjadi, sangat membuat frustrasi dan membingungkan. Jika Anda tidak tahu detail apa yang sedang terjadi, itu mungkin terlihat seperti bug kompiler. Fitur bahasa yang kelihatannya seperti bug penyusun, bukan sesuatu yang Anda inginkan.

Masukkan nullptr. Dalam C ++ 11, nullptr adalah kata kunci baru yang dapat (dan seharusnya!) Digunakan untuk mewakili pointer NULL; dengan kata lain, di mana pun Anda menulis NULL sebelumnya, Anda harus menggunakan nullptr. Tidak lagi jelas bagi Anda, programmer , (semua orang tahu apa arti NULL), tetapi lebih eksplisit untuk kompiler , yang tidak akan lagi melihat 0s di mana-mana digunakan untuk memiliki makna khusus ketika digunakan sebagai pointer.

Allain mengakhiri artikelnya dengan:

Terlepas dari semua ini - aturan praktis untuk C ++ 11 adalah untuk mulai menggunakan nullptrkapan saja Anda akan menggunakan NULLsebelumnya.

(Kata-kataku):

Terakhir, jangan lupa bahwa itu nullptradalah objek - kelas. Itu dapat digunakan di mana saja NULLdigunakan sebelumnya, tetapi jika Anda memerlukan jenisnya untuk beberapa alasan, jenisnya dapat diekstraksi dengan decltype(nullptr), atau langsung digambarkan sebagai std::nullptr_t, yang hanya merupakan typedefdari decltype(nullptr).

Referensi:

  1. Cprogramming.com: Jenis yang lebih baik di C ++ 11 - nullptr, kelas enum (enumerasi yang sangat diketik) dan cstdint
  2. https://en.cppreference.com/w/cpp/language/decltype
  3. https://en.cppreference.com/w/cpp/types/nullptr_t
Gabriel Staples
sumber
2
Saya harus mengatakan jawaban Anda diremehkan, sangat mudah dimengerti melalui teladan Anda.
mss
37

Ketika Anda memiliki fungsi yang dapat menerima pointer ke lebih dari satu jenis, memanggilnya dengan NULLambigu. Cara ini dikerjakan sekarang sangat berantakan dengan menerima int dan menganggapnya NULL.

template <class T>
class ptr {
    T* p_;
    public:
        ptr(T* p) : p_(p) {}

        template <class U>
        ptr(U* u) : p_(dynamic_cast<T*>(u)) { }

        // Without this ptr<T> p(NULL) would be ambiguous
        ptr(int null) : p_(NULL)  { assert(null == NULL); }
};

Di dalam C++11Anda akan dapat kelebihan pada nullptr_tsehingga ptr<T> p(42);akan menjadi kesalahan waktu kompilasi daripada run-time assert.

ptr(std::nullptr_t) : p_(nullptr)  {  }
Motti
sumber
Bagaimana jika NULLdidefinisikan sebagai 0L?
LF
9

nullptrtidak dapat ditugaskan ke tipe integral seperti inttetapi hanya tipe pointer; baik tipe built-in pointer seperti int *ptratau smart pointer sepertistd::shared_ptr<T>

Saya percaya ini adalah perbedaan penting karena NULLmasih dapat ditugaskan untuk kedua tipe integral dan pointer sebagaimana NULLmakro diperluas 0yang dapat berfungsi sebagai nilai awal untuk intserta pointer.

pengguna633658
sumber
Perhatikan bahwa jawaban ini salah. NULLtidak dijamin akan diperluas ke 0.
LF
6

Juga, apakah Anda memiliki contoh lain (di samping Wikipedia) di mana nullptrlebih baik dari 0 tua?

Iya. Ini juga contoh dunia nyata (disederhanakan) yang terjadi dalam kode produksi kami. Itu hanya menonjol karena gcc mampu mengeluarkan peringatan ketika crosscompiling ke platform dengan lebar register yang berbeda (masih tidak yakin persis mengapa hanya ketika crosscompiling dari x86_64 ke x86, ingatkan warning: converting to non-pointer type 'int' from NULL):

Pertimbangkan kode ini (C ++ 03):

#include <iostream>

struct B {};

struct A
{
    operator B*() {return 0;}
    operator bool() {return true;}
};

int main()
{
    A a;
    B* pb = 0;
    typedef void* null_ptr_t;
    null_ptr_t null = 0;

    std::cout << "(a == pb): " << (a == pb) << std::endl;
    std::cout << "(a == 0): " << (a == 0) << std::endl; // no warning
    std::cout << "(a == NULL): " << (a == NULL) << std::endl; // warns sometimes
    std::cout << "(a == null): " << (a == null) << std::endl;
}

Ini menghasilkan output ini:

(a == pb): 1
(a == 0): 0
(a == NULL): 0
(a == null): 1
Gabriel Schreiber
sumber
Saya gagal melihat bagaimana ini meningkat ketika menggunakan nullptr (dan C ++ 11). Jika Anda mengatur pb ke nullptr, perbandingan pertama mengevaluasi masih benar (sambil membandingkan apel dengan pir ..). Kasus kedua bahkan lebih buruk: Jika Anda membandingkan ke nullptr itu akan mengkonversi menjadi B * dan kemudian akan dievaluasi menjadi benar lagi (sebelum dicor ke bool dan expr dievaluasi ke false). Semuanya mengingatkan saya pada JavaScript dan saya ingin tahu apakah kita akan mendapatkan === dalam C ++ di masa depan :(
Nils
5

Nah, bahasa lain telah memesan kata-kata yang merupakan contoh jenis. Python, misalnya:

>>> None = 5
  File "<stdin>", line 1
SyntaxError: assignment to None
>>> type(None)
<type 'NoneType'>

Ini sebenarnya perbandingan yang cukup dekat karena Nonebiasanya digunakan untuk sesuatu yang belum diinternisasi, tetapi pada saat yang sama perbandingan seperti None == 0itu salah.

Di sisi lain, di dataran C, NULL == 0akan mengembalikan true IIRC karena NULLhanya makro mengembalikan 0, yang selalu merupakan alamat tidak valid (AFAIK).

Mark Rushakoff
sumber
4
NULLadalah makro yang mengembang ke nol, konstanta pelempar nol ke pointer menghasilkan pointer nol. Pointer nol tidak harus nol (tetapi sering), nol tidak selalu merupakan alamat yang tidak valid, dan nol yang tidak konstan untuk sebuah pointer tidak harus nol, dan pointer nol dilemparkan ke bilangan bulat tidak harus nol. Saya harap saya bisa melakukan itu tanpa melupakan apa pun. Referensi: c-faq.com/null/null2.html
Samuel Edwin Ward
3

Itu adalah kata kunci karena standar akan menentukannya seperti itu. ;-) Menurut konsep publik terbaru (n2914)

2.14.7 Pointer literals [lex.nullptr]

pointer-literal:
nullptr

Pointer literal adalah kata kunci nullptr. Ini adalah nilai jenis std::nullptr_t.

Ini berguna karena tidak secara implisit dikonversi ke nilai integral.

KTC
sumber
2

Katakanlah Anda memiliki fungsi (f) yang kelebihan beban untuk mengambil int dan char *. Sebelum C ++ 11, Jika Anda ingin menyebutnya dengan pointer nol, dan Anda menggunakan NULL (yaitu nilai 0), maka Anda akan memanggil yang kelebihan beban untuk int:

void f(int);
void f(char*);

void g() 
{
  f(0); // Calls f(int).
  f(NULL); // Equals to f(0). Calls f(int).
}

Ini mungkin bukan yang Anda inginkan. C ++ 11 memecahkan ini dengan nullptr; Sekarang Anda dapat menulis yang berikut ini:

void g()
{
  f(nullptr); //calls f(char*)
}
Amit G.
sumber
1

Biarkan saya memberi Anda implementasi tidak canggih nullptr_t

struct nullptr_t 
{
    void operator&() const = delete;  // Can't take address of nullptr

    template<class T>
    inline operator T*() const { return 0; }

    template<class C, class T>
    inline operator T C::*() const { return 0; }
};

nullptr_t nullptr;

nullptradalah contoh halus dari idiom Return Type Resolver untuk secara otomatis menyimpulkan pointer nol dari tipe yang benar tergantung pada jenis instance yang ditugaskan kepadanya.

int *ptr = nullptr;                // OK
void (C::*method_ptr)() = nullptr; // OK
  • Seperti yang Anda bisa di atas, ketika nullptrsedang ditugaskan ke pointer integer, intjenis instantiation dari fungsi konversi templatized dibuat. Dan hal yang sama juga berlaku untuk pointer metode.
  • Dengan cara ini dengan memanfaatkan fungsionalitas templat, kami sebenarnya membuat jenis pointer nol yang tepat setiap kali kami melakukannya, penetapan jenis baru.
  • Seperti nullptrbilangan bulat integer dengan nilai nol, Anda tidak dapat menggunakan alamatnya yang kami capai dengan menghapus & operator.

Mengapa kita perlu nullptrdi tempat pertama?

  • Anda melihat tradisional NULLmemiliki beberapa masalah dengan itu seperti di bawah ini:

1️⃣ Konversi tersirat

char *str = NULL; // Implicit conversion from void * to char *
int i = NULL;     // OK, but `i` is not pointer type

2️⃣ Fungsi memanggil ambiguitas

void func(int) {}
void func(int*){}
void func(bool){}

func(NULL);     // Which one to call?
  • Kompilasi menghasilkan kesalahan berikut:
error: call to 'func' is ambiguous
    func(NULL);
    ^~~~
note: candidate function void func(bool){}
                              ^
note: candidate function void func(int*){}
                              ^
note: candidate function void func(int){}
                              ^
1 error generated.
compiler exit status 1

3️⃣ Konstruktor kelebihan beban

struct String
{
    String(uint32_t)    {   /* size of string */    }
    String(const char*) {       /* string */        }
};

String s1( NULL );
String s2( 5 );
  • Dalam kasus seperti itu, Anda perlu pemeran eksplisit (mis  String s((char*)0)).
Vishal Chovatiya
sumber
0

0 digunakan sebagai satu-satunya nilai integer yang dapat digunakan sebagai inisialisasi bebas-cor untuk pointer: Anda tidak dapat menginisialisasi pointer dengan nilai integer lain tanpa gips. Anda dapat menganggap 0 sebagai singleton consexpr secara sintaksis mirip dengan integer literal. Itu dapat memulai setiap pointer atau integer. Tetapi yang mengejutkan, Anda akan menemukan bahwa itu tidak memiliki tipe yang berbeda: itu adalah int. Jadi, mengapa 0 dapat menginisialisasi pointer dan 1 tidak bisa? Jawaban praktisnya adalah kita perlu sarana untuk mendefinisikan nilai null pointer dan konversi implisit langsung intke pointer rentan kesalahan. Jadi 0 menjadi makhluk aneh aneh yang aneh dari era prasejarah. nullptrdiusulkan menjadi representasi constexpr tunggal nyata dari nilai nol untuk menginisialisasi pointer. Itu tidak dapat digunakan untuk secara langsung menginisialisasi bilangan bulat dan menghilangkan ambiguitas yang terlibat dengan mendefinisikan NULLdalam hal 0. nullptrdapat didefinisikan sebagai perpustakaan menggunakan sintaks std tetapi secara semantik tampak menjadi komponen inti yang hilang. NULLsekarang tidak digunakan lagi nullptr, kecuali beberapa perpustakaan memutuskan untuk mendefinisikannya sebagai nullptr.

Merah
sumber
-1

Inilah tajuk LLVM.

// -*- C++ -*-
//===--------------------------- __nullptr --------------------------------===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef _LIBCPP_NULLPTR
#define _LIBCPP_NULLPTR

#include <__config>

#if !defined(_LIBCPP_HAS_NO_PRAGMA_SYSTEM_HEADER)
#pragma GCC system_header
#endif

#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

#endif  // _LIBCPP_NULLPTR

(Banyak yang bisa diungkap dengan cepat grep -r /usr/include/*`)

Satu hal yang melompat keluar adalah *kelebihan operator (mengembalikan 0 jauh lebih ramah daripada segfaulting ...). Hal lain adalah tidak terlihat kompatibel dengan menyimpan alamat sama sekali . Yang, dibandingkan dengan bagaimana cara slinging void * dan memberikan hasil NULL ke pointer normal sebagai nilai sentinel, jelas akan mengurangi faktor "jangan lupa, itu mungkin bom".

lk
sumber
-2

NULL tidak perlu 0. Selama Anda menggunakan selalu NULL dan tidak pernah 0, NULL dapat berupa nilai apa pun. Dengan asumsi Anda memprogram Mikrokontroler von Neuman dengan memori datar, yang memiliki vektor interruptnya pada 0. Jika NULL adalah 0 dan sesuatu menulis pada NULL Pointer maka Mikrokontroler mogok. Jika NULL misalkan 1024 dan pada 1024 ada variabel yang dilindungi undang-undang, maka penulisan tidak akan merusaknya, dan Anda dapat mendeteksi penetapan NULL Pointer dari dalam program. Ini tidak ada gunanya pada PC, tetapi untuk pesawat ruang angkasa, peralatan militer atau medis, penting untuk tidak menabrak.

Axel Schweiß
sumber
2
Nah, nilai aktual dari null pointer di memori mungkin tidak nol, tetapi standar C (dan C ++) mengamanatkan kompiler untuk mengkonversi integral 0 literal ke null pointer.
bzim