Как работает реализация 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.
Крис

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

1
@LF Технически требует ли стандарт, чтобы фундаментальный тип не являлся типом класса?
eerorika

Ответы:


20

Мне любопытно узнать, как работает nullptr.

Он работает самым простым способом: по указу . Это работает, потому что стандарт 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 ++, потому что она является частью базового языка.
Спенсер,

1
@ Спенсер: Нет, я имел в виду именно то, что сказал: язык C ++ не может быть использован для реализации типа, который выполняет все, что std::nullptr_tтребуется. Как и C ++, язык не может реализовать тип, который выполняет все, что intтребуется.
Никол Болас
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.