Nullptr tam olarak nedir?


570

Artık birçok yeni özelliğe sahip C ++ 11 var. İlginç ve kafa karıştırıcı biri (en azından benim için) yenidir nullptr.

Kötü makroya artık gerek yok NULL.

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

Yine de, nasıl nullptrçalıştığını anlamıyorum . Örneğin, Wikipedia makalesinde şunlar yazılıdır:

C ++ 11 , ayırt edici bir boş işaretçi sabiti olarak hizmet etmek için yeni bir anahtar kelime ekleyerek bunu düzeltir: nullptr. Bu ait tipi nullptr_t herhangi bir işaretçi türü veya işaretçi-üyesinin türüne örtük olarak dönüştürülebilir ve karşılaştırılabilir. Bool dışında dolaylı olarak dönüştürülebilir veya integral tiplerle karşılaştırılamaz.

Anahtar kelime ve türün bir örneği nasıl?

Ayrıca, nullptriyi eskiye göre daha üstün olan başka bir örneğiniz var mı (Wikipedia'nın yanında) 0?


23
ilgili gerçek: nullptrC ++ / CLI'da yönetilen tanıtıcılar için null referansı temsil etmek için de kullanılır.
Mehrdad Afshari

3
Visual C ++ kullanırken, yerel C / C ++ koduyla nullptr kullanırsanız ve sonra / clr derleyici seçeneğiyle derlerseniz, derleyicinin nullptr öğesinin yerel veya yönetilen bir boş işaretçi değerini gösterip göstermediğini belirleyemediğini unutmayın. Niyetinizi derleyiciye açık hale getirmek için, yönetilen bir değer belirtmek için nullptr veya yerel bir değer belirtmek için __nullptr tuşlarını kullanın. Microsoft bunu bir bileşen uzantısı olarak uygulamıştır.
cseder

6
Is nullptr_t, yalnızca bir kişide görülmesi garantili nullptr? Yani, bir işlev döndürülürse nullptr_t, derleyici işlevin gövdesine bakılmaksızın hangi değerin döndürüleceğini zaten biliyordur?
Aaron McDaid

8
@AaronMcDaid std::nullptr_tsomutlaştırılabilir, ancak nullptrtür olarak tanımlandığından tüm örnekler aynı olacaktır typedef decltype(nullptr) nullptr_t. Tipin var olmasının birincil nedeninin, işlevlerin nullptrgerekirse yakalamak için özellikle aşırı yüklenebilmesi olduğuna inanıyorum . Bir örnek için buraya bakın .
Justin Time - Monica

5
0 boş işaretçi hiç boş gösterici ile elde edilebilir bir işaretçidir döküm işaretçi tipine sıfır Birebir ve bu işaret etmez bir tanımla mevcut nesne.
Swift - Cuma Pastası

Yanıtlar:


403

Anahtar kelime ve türün bir örneği nasıl?

Bu şaşırtıcı değil. Hem trueve falseanahtar kelimelerdir ve değişmez değerler olarak bir türü ( bool) vardır. türünde nullptrbir işaretçi değişmezidirstd::nullptr_t ve bir ön değerdir (adresini kullanarak alamazsınız &).

  • 4.10pointer dönüştürme hakkında bir tür ön std::nullptr_tdeğerinin bir boş işaretçi sabiti olduğunu ve bir entegre boş işaretçi sabitinin dönüştürülebileceğini söylüyor std::nullptr_t. Ters yöne izin verilmez. Bu, hem işaretçiler hem de tamsayılar için bir işlevin aşırı yüklenmesine nullptrve işaretçi sürümünü seçmek için geçilmesine izin verir . Sürümü geçiyor NULLveya 0kafa karıştırıcı bir şekilde intsürümü seçiyor.

  • nullptr_tİntegral tipte bir kadro a'ya ihtiyaç duyar reinterpret_castve integral tipe kadro ile aynı anlambilime sahiptir (void*)0(eşleme uygulaması tanımlanmıştır). A reinterpret_cast, nullptr_therhangi bir işaretçi türüne dönüştürülemez . Mümkünse örtük dönüştürmeye güvenin veya kullanın static_cast.

  • Standart bunun sizeof(nullptr_t)olmasını gerektirir sizeof(void*).


Oh, baktıktan sonra bana öyle geliyor ki, koşullu operatör 0 gibi durumlarda nullptr'e dönüşemiyor cond ? nullptr : 0;. Cevabımdan kaldırıldı.
Johannes Schaub - litb

88
Bunun NULLgaranti edilmediğini bile unutmayın 0. Bu 0Ldurumda bir çağrı void f(int); void f(char *);belirsiz olabilir. nullptrher zaman işaretçi sürümünü tercih eder ve hiçbir zaman işaretçi sürümünü çağırmaz int. Ayrıca not nullptr olduğunu dönüştürülebilir bool(taslak de söylüyor 4.12).
Johannes Schaub - litb

@litb: f (int) ve f (void *) ile ilgili olarak - f (0) hala belirsiz mi olacak?
Steve Folly

27
@Steve, hayır bu intsürümü çağıracak . Ama f(0L), çünkü belirsiz long -> intaswell long -> void*ikisi de eşit derecede maliyetlidir. Eğer 0Lderleyicinizde NULL varsa , f(NULL)bu iki işlev göz önüne alındığında bir çağrı belirsiz olacaktır. nullptrTabii ki öyle değil .
Johannes Schaub - litb

2
@SvenS (void*)0C ++ 'da olduğu gibi tanımlanmamalıdır . Ancak, 0 değeri ile herhangi bir integral sabitinin nullptryerine getirdiği herhangi bir keyfi boş işaretçi sabiti olarak tanımlanabilir . Yani, kesinlikle değil will ama can . (Bana btw ping yapmayı unuttun ..)
Deduplicator

60

Gönderen nullptr: A Tipi güvenli ve Clear-Cut Boş İşaretçi :

Yeni C ++ 09 nullptr anahtar sözcüğü, buggy ve zayıf yazılan değişmez değeri 0 ve rezil NULL makrosunun yerini alan, evrensel bir boş gösterici hazır bilgisi olarak hizmet veren bir değer sabitini belirtir. nullptr böylece 30 yıldan fazla utanç, belirsizlik ve hatalara son verir. Aşağıdaki bölümler nullptr özelliğini sunar ve NULL ve 0 rahatsızlıklarını nasıl giderebileceğini gösterir.

Diğer referanslar:


17
C ++ 09? Ağustos 2011'den önce C ++ 0x olarak adlandırılmadı mı?
Michael Dorst

2
@anthropomorphic İşte amacı bu. C ++ 0x, hala devam eden çalışma sırasında kullanıldı, çünkü 2008 veya 2009'un bitip bitmeyeceği bilinmiyordu. Aslında C ++ 11B anlamına geliyor. Bkz. Stroustrup.com/C++11FAQ.html
mxmlnkn

44

Neden C ++ 11'de nullptr? Bu ne? NULL neden yeterli değil?

C ++ uzmanı Alex Allain burada mükemmel bir şekilde söylüyor (vurgularım kalın olarak eklendi):

... aşağıdaki iki işlev bildirimine sahip olduğunuzu düşünün:

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

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

Her ne kadar ikinci fonksiyon çağrılmış gibi görünse de - sonuçta, bir işaretçi gibi görünen şeyleri geçiyorsunuz - gerçekten çağrılacak ilk fonksiyon! Sorun, NULL 0 ve 0 bir tamsayı olduğu için, bunun yerine func'un ilk sürümünün çağrılmasıdır. Bu, evet, her zaman gerçekleşmeyen türden bir şeydir, ancak gerçekleştiğinde son derece sinir bozucu ve kafa karıştırıcıdır. Neler olup bittiğini bilmiyorsanız, derleyici hatası gibi görünebilir. Derleyici hataya benzeyen bir dil özelliği, istediğiniz bir şey değildir.

Nullptr girin. C ++ 11'de nullptr, NULL işaretçileri temsil etmek için kullanılabilen (ve olması gereken!) Yeni bir anahtar kelimedir; diğer bir deyişle, daha önce NULL yazdığınız her yerde, bunun yerine nullptr kullanmalısınız. Programcı sizin için daha açık değil (herkes NULL'un ne anlama geldiğini biliyor), ancak derleyici için daha açık , bu da artık her yerde 0'ların işaretçi olarak kullanıldığında özel bir anlama sahip olduğunu görmeyecek.

Allain makalesini şu şekilde bitirir:

Tüm bunlardan bağımsız olarak - C ++ 11 için başparmak kuralı , geçmişte nullptrbaşka türlü kullanacağınız zaman kullanmaya başlamaktır NULL.

(Kelimelerim):

Son olarak, nullptrbunun bir nesne - bir sınıf olduğunu unutmayın . Her yerde kullanılabilir NULLönce kullanılan, ancak nedense türünü gerekiyorsa, 's tipi ile elde edilebilir decltype(nullptr), ya da doğrudan olarak tanımlanan std::nullptr_tbasit bir olan typedefbir decltype(nullptr).

Referanslar:

  1. C ++ 11'de daha iyi türler - nullptr, enum sınıfları (kuvvetle yazılan numaralandırmalar) ve cstdint
  2. https://en.cppreference.com/w/cpp/language/decltype
  3. https://en.cppreference.com/w/cpp/types/nullptr_t

2
Cevabınızın önemsiz olduğunu söylemeliyim, örneğin üzerinden anlamanız çok kolaydı.
mss

37

Birden fazla türe işaretçi alabilen bir işleve sahipseniz, onu çağırmak NULLbelirsizdir. Bunun şu anda nasıl çalışılacağı bir int'i kabul ederek ve varsayarak çok acayip 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); }
};

İçinde C++11aşırı yükleme yapabileceksiniz, nullptr_tböylece ptr<T> p(42);çalışma zamanı yerine derleme zamanı hatası olacaktır assert.

ptr(std::nullptr_t) : p_(nullptr)  {  }

Ya NULLtanımlanırsa 0L?
LF

9

nullptrintyalnızca bir işaretçi türü gibi bir integral türüne atanamaz ; gibi yerleşik bir işaretçi türü int *ptrveya gibi akıllı bir işaretçistd::shared_ptr<T>

Bu önemli bir ayrım olduğuna inanıyorum, çünkü NULLhem bir hem de bir işaretçi NULLiçin 0hem intbir işaretçi hem de bir işaretçi için bir başlangıç ​​değeri olarak hizmet edebilen genişletilmiş bir makro olarak atanabilir .


Bu cevabın yanlış olduğunu unutmayın. NULLdeğerinin genişletileceği garanti edilmez 0.
LF

6

Ayrıca, nullptriyi eski 0'dan daha üstün olan başka bir örneğiniz var mı (Wikipedia'nın yanında) ?

Evet. Aynı zamanda üretim kodumuzda meydana gelen (basitleştirilmiş) gerçek dünya örneği. Bu sadece göze çarpıyordu çünkü gcc, farklı kayıt genişliğine sahip bir platforma çapraz derleme yaparken bir uyarı verebildi (hala neden sadece x86_64'ten x86'ya çapraz derleme yaparken tam olarak emin değilim, uyarıyor warning: converting to non-pointer type 'int' from NULL):

Bu kodu düşünün (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;
}

Bu çıktıyı verir:

(a == pb): 1
(a == 0): 0
(a == NULL): 0
(a == null): 1

Nullptr (ve C ++ 11) kullanırken bu nasıl geliştiğini göremiyorum. Eğer pb'yi nullptr olarak ayarlarsanız, ilk karşılaştırma hala doğru olarak değerlendirilir (elma ile armut karşılaştırılırken ..). İkinci durum daha da kötüdür: Eğer bir nullptr ile karşılaştırırsanız, a, B * 'ye dönüştürülecek ve sonra tekrar true değerine (boole dökülmeden ve ifade yanlış olarak değerlendirilmeden) değerlendirilecektir. Her şey bana JavaScript'i hatırlatıyor ve gelecekte C ++ 'da === alacağımızı merak ediyorum :(
Nils

5

Diğer diller, tür örnekleri olan ayrılmış sözcüklere sahiptir. Python, örneğin:

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

Bu aslında oldukça yakın bir karşılaştırma çünkü Nonetipik olarak intialized olmayan bir şey için kullanılır, ancak aynı zamanda None == 0yanlış gibi karşılaştırmalar .

Öte yandan, C NULL == 0düzünde NULL, her zaman geçersiz bir adres (AFAIK) olan 0 döndüren bir makro olduğu için gerçek IIRC döndürür.


4
NULLsıfıra genişleyen bir makro, bir işaretçiye sabit bir sıfır atımı boş bir işaretçi üretir. Bir boş gösterici sıfır olmak zorunda değildir (ancak çoğu zaman), sıfır her zaman geçersiz bir adres değildir ve bir işaretçiye sabit olmayan bir sıfır atımı null olmak zorunda değildir ve bir boş gösterici bir tamsayı sıfır olmak zorunda değildir. Umarım hiçbir şeyi unutmadan bunu hallederim. Referans: c-faq.com/null/null2.html
Samuel Edwin Ward

3

Bu bir anahtar kelimedir çünkü standart bunu böyle belirtecektir. ;-) En son kamu taslağına göre (n2914)

2.14.7 İşaretçi değişmez değerleri [lex.nullptr]

pointer-literal:
nullptr

İşaretçi değişmezi anahtar kelimedir nullptr. Bu bir tür değerdir std::nullptr_t.

Yararlı bir integral değerine dönüşmediği için yararlıdır.


2

Diyelim ki hem int hem de char * almak için aşırı yüklenmiş bir fonksiyonunuz (f) var. C ++ 11'den önce, bir boş gösterici ile çağırmak istiyorsanız ve NULL (yani 0 değeri) kullandıysanız, int için aşırı yüklenmiş olanı çağırırsınız:

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

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

Muhtemelen istediğin bu değil. C ++ 11 bunu nullptr ile çözer; Şimdi aşağıdakileri yazabilirsiniz:

void g()
{
  f(nullptr); //calls f(char*)
}

1

Önce size deneyimsiz bir uygulama vereyim 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;

nullptr, atadığı örneğin türüne bağlı olarak, doğru türde boş bir işaretçi otomatik olarak çıkarmak için Dönüş Türü Çözümleyici deyiminin ince bir örneğidir .

int *ptr = nullptr;                // OK
void (C::*method_ptr)() = nullptr; // OK
  • Yukarıda yapabileceğiniz gibi, nullptrbir tamsayı işaretçisine atandığında int, geçici dönüştürme işlevinin bir tür örneği oluşturulur. Aynı şey yöntem işaretçileri için de geçerlidir.
  • Bu şekilde, şablon işlevselliğinden yararlanarak, her yaptığımızda yeni bir tür ataması için uygun türde boş gösterici oluşturuyoruz.
  • nullptrSıfır değerine sahip bir tamsayı değişmezi olduğu gibi , & operatörünü silerek elde ettiğimiz adresini kullanamazsınız.

nullptrİlk etapta neden ihtiyacımız var ?

  • Gelenekselin NULLbununla ilgili bir sorunu olduğunu görüyorsunuz :

1️⃣ Örtük dönüşüm

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

2️⃣ İşlev çağırma belirsizliği

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

func(NULL);     // Which one to call?
  • Derleme aşağıdaki hatayı üretir:
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️⃣ Yapıcı aşırı yüklenmesi

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

String s1( NULL );
String s2( 5 );
  • Bu gibi durumlarda, açık bir şekilde yayınlamanız gerekir (ör  String s((char*)0)).

0

0, işaretçiler için cast içermeyen bir başlatıcı olarak kullanılabilen tek tam sayı değeriydi: işaretleri, cast olmadan diğer tamsayı değerleriyle başlatamazsınız. 0'ı sözdizimsel olarak bir tamsayı değişmezine benzeyen bir bitişik tekil olarak düşünebilirsiniz. Herhangi bir işaretçi veya tamsayı başlatabilir. Ama şaşırtıcı bir şekilde, farklı bir türü olmadığını göreceksiniz: bu bir int. Öyleyse 0 nasıl işaretçileri başlatabilir ve 1 nasıl başlatamaz? Pratik bir cevap, işaretçi boş değerini tanımlamak için bir araca ihtiyaç duymamız ve intbir işaretçiye doğrudan örtük dönüştürmenin hataya eğilimli olmasıydı. Böylece 0, tarih öncesi dönemden gerçek bir ucube garip canavarı oldu. nullptrişaretçileri başlatmak için null değerin gerçek bir singleton constexpr temsilidir. Tamsayıları doğrudan başlatmak ve NULL0 cinsinden tanımlamaya ilişkin belirsizlikleri ortadan kaldırmak için nullptrkullanılamaz. Std sözdizimi kullanan bir kütüphane olarak tanımlanabilir, ancak anlamsal olarak eksik bir çekirdek bileşen olarak görünebilir. bazı kütüphaneler onu tanımlamaya karar vermedikçe NULL, şimdi lehine itiraz nullptrediliyor nullptr.


-1

İşte LLVM başlığı.

// -*- 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

(hızlı bir şekilde çok şey açığa çıkarılabilir grep -r /usr/include/*`)

Sıçrayan bir şey, operatörün *aşırı yüklenmesidir (0 döndürmek, segfaulting'ten çok daha dostudur ...). Başka bir şey bir adres depolama ile uyumlu görünmüyor olduğu hiç . Hangi boşluk * sapan ve NULL sonuçları nöbetçi değerleri olarak normal işaretçiler geçirme karşılaştırıldığında, açıkçası "asla unutma, bir bomba olabilir" faktörünü azaltacaktır.


-2

NULL değerinin 0 olması gerekmez. Her zaman NULL ve asla 0 kullandığınız sürece, NULL herhangi bir değer olabilir. Düz bellekli bir von Neuman Mikrodenetleyici programladığınız varsayıldığında, kesme vektörleri 0'dadır. NULL 0 ise ve bir şey NULL İşaretçiye yazarsa Mikrodenetleyici çöker. NULL, 1024 diyelim ve 1024'te ayrılmış bir değişken varsa, yazma işlemi çökmez ve NULL İşaretçi atamalarını programın içinden algılayabilirsiniz. Bu PC'lerde anlamsızdır, ancak uzay sondaları, askeri veya tıbbi ekipman için çökmemek önemlidir.


2
Eh, bellekteki boş göstergenin gerçek değeri sıfır olmayabilir, ancak C (ve C ++) standardı derleyicileri integral 0 değişmezini boş göstericiye dönüştürmek için zorunlu kılar.
bzim
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.