Як працює впровадження c ++ nullptr?


13

Мені цікаво знати, як nullptrпрацює. Стандарти N4659 і N4849 кажуть:

  1. він повинен мати тип std::nullptr_t;
  2. ви не можете прийняти його адресу;
  3. його можна безпосередньо перетворити на вказівник і вказівник на член;
  4. sizeof(std::nullptr_t) == sizeof(void*);
  5. його перетворення в boolє false;
  6. його значення може бути перетворене на інтегральний тип однаково (void*)0, але не назад;

Таким чином, це в основному константа з тим же значенням, що і (void*)0, але має інший тип. Я знайшов реалізацію std::nullptr_tна своєму пристрої, і це наступним чином.

#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

Мене більше цікавить перша частина. Здається, це задовольняє пункти 1-5, але я поняття не маю, чому він має підклас __nat і все, що з ним пов'язано. Я також хотів би знати, чому це не вдається при цілісних перетвореннях.

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є фундаментальним типом. Як intреалізується?
LF

9
Примітка #ifdef _LIBCPP_HAS_NO_NULLPTR. Це здається найкращим рішенням, коли компілятор не забезпечує nullptr.
chris

5
@Fullfungo Стандарт говорить, що nullptr_tце фундаментальний тип. Реалізація його як тип класу не здійснює відповідну реалізацію. Дивіться коментар Кріса.
LF

1
@LF Чи стандарт вимагає технічно, що фундаментальний тип не є класовим типом?
eerorika

Відповіді:


20

Мені цікаво знати, як працює nullptr.

Це працює найпростішим можливим способом: fiat . Це працює тому, що стандарт C ++ каже, що він працює, і він працює так, як це робиться, тому що стандарт C ++ говорить, що реалізації повинні змусити його працювати таким чином.

Важливо визнати, що неможливо реалізувати, std::nullptr_tвикористовуючи правила мови C ++. Перетворення з нульової постійної вказівника типу std::nullptr_tв покажчик не є визначеною користувачем перетворенням. Це означає, що ви можете перейти від нульової постійної вказівника до вказівника, а потім через визначене користувачем перетворення до іншого типу, і все це в одній неявній послідовності перетворення.

Це неможливо, якщо ви реалізуєте nullptr_tяк клас. Оператори перетворень представляють визначені користувачем перетворення, і неявні правила послідовності перетворень C ++ не дозволяють проводити більше однієї визначеної користувачем перетворення в такій послідовності.

Отже, опублікований вами код є приємним наближенням std::nullptr_t, але це не що інше. Це не законна реалізація типу. Це, мабуть, старіша версія компілятора (залишена з причин зворотної сумісності), перш ніж компілятор забезпечив належну підтримку std::nullptr_t. Ви можете побачити це тим , що #defineS nullptr, в той час як C ++ 11 говорить , що nullptrце ключове слово , а НЕ макрос.

C ++ не може реалізувати std::nullptr_t, як і C ++ не може реалізувати intабо void*. Тільки реалізація може реалізувати ці речі. Саме це робить його «фундаментальним типом»; це частина мови .


його значення може бути перетворене в інтегральний тип ідентично (void *) 0, але не назад;

Немає неявного перетворення з нульової константи вказівника в цілісні типи. Існує перетворення з 0інтегрального типу, але це тому, що це цілий буквальний нуль, який є ... цілим числом.

nullptr_tможе бути передано до цілого типу (через reinterpret_cast), але воно може бути неявно перетворене на покажчики та на bool.


4

Що означає "неможливо реалізувати std :: nullptr_t, використовуючи правила мови C ++"? Це означає, що компілятор C ++ не може бути повністю записаний в C ++ (я думаю, що ні)?
північ

3
@northerner: Я маю на увазі, що ви не можете записати тип, який є рівнозначним поведінці, яка вимагається std::nullptr_t. Так само, як ви не можете написати тип, що відповідає рівноправній поведінці int. Можна наблизитись, але все одно будуть суттєві відмінності. І я не говорю про детектори ознак, подібні до is_classтого, що це означає, що ваш тип призначений для користувача. Існують речі щодо необхідної поведінки основних типів, які ви просто не можете скопіювати, використовуючи правила мови.
Нікол Болас

1
Просто формулювання каламбуру. Коли ви говорите "C ++ не може реалізувати nullptr_t", ви говорите занадто широко. А висловлювання "лише реалізація може реалізувати це" лише заплутує справи. Що ви маєте на увазі, це nullptr_tнеможливо реалізувати " в бібліотеці C ++, оскільки це частина основної мови.
Spencer

1
@Spencer: Ні, я мав на увазі саме те, що я сказав: C ++ мову не можна використовувати для реалізації типу, який виконує все, що std::nullptr_tпотрібно зробити. Так само як C ++, мова не може реалізувати тип, який виконує все, що intпотрібно зробити.
Нікол Болас
Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.