Come funziona l'implementazione di c ++ nullptr?


13

Sono curioso di sapere come nullptrfunziona. Gli standard N4659 e N4849 dicono:

  1. deve avere tipo std::nullptr_t;
  2. non puoi prendere il suo indirizzo;
  3. può essere convertito direttamente in un puntatore e un puntatore in membro;
  4. sizeof(std::nullptr_t) == sizeof(void*);
  5. la sua conversione in boolè false;
  6. il suo valore può essere convertito in tipo integrale in modo identico (void*)0, ma non al contrario;

Quindi è sostanzialmente una costante con lo stesso significato di (void*)0, ma ha un tipo diverso. Ho trovato l'implementazione di std::nullptr_tsul mio dispositivo ed è la seguente.

#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

Sono più interessato alla prima parte però. Sembra soddisfare i punti 1-5, ma non ho idea del perché abbia una sottoclasse __nat e tutto ciò che è collegato. Vorrei anche sapere perché non riesce nelle conversioni integrali.

struct nullptr_t2{
    void* __lx;
    struct __nat {int __for_bool_;};
     constexpr nullptr_t2() : __lx(0) {}
     constexpr nullptr_t2(int __nat::*) : __lx(0) {}
     constexpr operator int __nat::*() const {return 0;}
    template <class _Tp>
         constexpr
        operator _Tp* () const {return 0;}
    template <class _Tp, class _Up>
        operator _Tp _Up::* () const {return 0;}
    friend  constexpr bool operator==(nullptr_t2, nullptr_t2) {return true;}
    friend  constexpr bool operator!=(nullptr_t2, nullptr_t2) {return false;}
};
inline constexpr nullptr_t2 __get_nullptr_t2() {return nullptr_t2(0);}
#define nullptr2 __get_nullptr_t2()

int main(){
    long l  = reinterpret_cast<long>(nullptr);
    long l2 = reinterpret_cast<long>(nullptr2); // error: invalid type conversion
    bool b  = nullptr; // warning: implicit conversion
                       // edditor error: a value of type "std::nullptr_t" cannot be used to initialize an entity of type "bool"
    bool b2 = nullptr2;
    if (nullptr){}; // warning: implicit conversion
    if (nullptr2){};
};

2
nullptr_tè un tipo fondamentale. Come viene intimplementato?
LF

9
Nota #ifdef _LIBCPP_HAS_NO_NULLPTR. Sembra una soluzione alternativa per il momento in cui il compilatore non fornisce nullptr.
chris,

5
@Fullfungo Lo standard dice che nullptr_tè un tipo fondamentale. L'implementazione come tipo di classe non costituisce un'implementazione conforme. Vedi il commento di chris.
LF

1
@LF La norma richiede tecnicamente che un tipo fondamentale non sia un tipo di classe?
Eerorika,

2
@eerorika: is_classe is_null_pointernon possono essere entrambi veri per lo stesso tipo. Solo una delle funzioni della categoria di tipo primario può restituire true per un tipo specifico.
Nicol Bolas,

Risposte:


20

Sono curioso di sapere come funziona nullptr.

Funziona nel modo più semplice possibile: da Fiat . Funziona perché lo standard C ++ dice che funziona e funziona come lo fa perché lo standard C ++ afferma che le implementazioni devono farlo funzionare in quel modo.

È importante riconoscere che è impossibile implementare std::nullptr_tusando le regole del linguaggio C ++. La conversione da una costante di puntatore null di tipo std::nullptr_tin un puntatore non è una conversione definita dall'utente. Ciò significa che puoi passare da una costante di puntatore null a un puntatore, quindi attraverso una conversione definita dall'utente in un altro tipo, il tutto in un'unica sequenza di conversione implicita.

Questo non è possibile se si implementa nullptr_tcome classe. Gli operatori di conversione rappresentano conversioni definite dall'utente e le regole implicite della sequenza di conversione di C ++ non consentono più di una conversione definita dall'utente in tale sequenza.

Quindi il codice che hai pubblicato è una buona approssimazione di std::nullptr_t, ma non è altro. Non è un'implementazione legittima del tipo. Questo probabilmente proveniva da una versione precedente del compilatore (lasciato per motivi di compatibilità con le versioni precedenti) prima che il compilatore fornisse il supporto adeguato per std::nullptr_t. Lo puoi vedere dal fatto che lo #defineè nullptr, mentre C ++ 11 dice che nullptrè una parola chiave , non una macro.

C ++ non può implementare std::nullptr_t, proprio come C ++ non può implementare into void*. Solo l'implementazione può implementare queste cose. Questo è ciò che lo rende un "tipo fondamentale"; fa parte della lingua .


il suo valore può essere convertito in tipo integrale in modo identico a (void *) 0, ma non al contrario;

Non esiste una conversione implicita da una costante puntatore null in tipi integrali. C'è una conversione da 0in un tipo integrale, ma è perché è lo zero letterale intero, che è ... un numero intero.

nullptr_tpuò essere eseguito il cast in un tipo intero (via reinterpret_cast), ma può essere implicitamente convertito solo in puntatori e in bool.


4
@Wyck: " fiat "
Nicol Bolas,

Cosa si intende per "è impossibile implementare std :: nullptr_t usando le regole del linguaggio C ++"? Significa che un compilatore C ++ non può essere completamente scritto in C ++ stesso (suppongo di no)?
Nord,

3
@northerner: voglio dire che non puoi scrivere un tipo esattamente equivalente al comportamento richiesto da std::nullptr_t. Proprio come non è possibile scrivere un tipo esattamente equivalente al comportamento richiesto di int. Puoi avvicinarti, ma ci saranno comunque differenze significative. E non sto parlando di rilevatori di tratti come quelli is_classche mostrano che il tuo tipo è definito dall'utente. Ci sono cose sul comportamento richiesto dei tipi fondamentali che semplicemente non puoi copiare usando le regole della lingua.
Nicol Bolas,

1
Solo un cavillo di formulazione. Quando dici "C ++ non può implementare nullptr_t" parli in modo troppo ampio. E dire "solo l'implementazione può implementarla" confonde solo le cose. Quello che vuoi dire è che nullptr_tnon può essere implementato " nella libreria C ++ perché fa parte del linguaggio di base.
Spencer,

1
@Spencer: No, intendevo esattamente quello che ho detto: C ++ il linguaggio non può essere usato per implementare un tipo che fa tutto ciò che std::nullptr_tè necessario fare. Proprio come C ++ il linguaggio non può implementare un tipo che fa tutto ciò che intè necessario fare.
Nicol Bolas,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.