Làm thế nào để thực hiện c ++ nullptr hoạt động?


13

Tôi tò mò muốn biết làm thế nào nullptrhoạt động. Tiêu chuẩn N4659 và N4849 nói:

  1. nó phải có loại std::nullptr_t;
  2. bạn không thể lấy địa chỉ của nó;
  3. nó có thể được chuyển đổi trực tiếp thành một con trỏ và con trỏ thành thành viên;
  4. sizeof(std::nullptr_t) == sizeof(void*);
  5. chuyển đổi sang boolfalse;
  6. giá trị của nó có thể được chuyển đổi thành loại tích phân giống hệt (void*)0, nhưng không ngược lại;

Vì vậy, về cơ bản nó là một hằng số có cùng ý nghĩa (void*)0, nhưng nó có một loại khác. Tôi đã tìm thấy việc thực hiện std::nullptr_ttrên thiết bị của mình và nó như sau.

#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

Tôi quan tâm nhiều hơn trong phần đầu tiên mặc dù. Nó dường như thỏa mãn các điểm 1-5, nhưng tôi không biết tại sao nó lại có một lớp con __nat và mọi thứ liên quan đến nó. Tôi cũng muốn biết lý do tại sao nó thất bại trên các chuyển đổi tích hợp.

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_tlà một loại cơ bản. Làm thế nào được intthực hiện?
LF

9
Lưu ý #ifdef _LIBCPP_HAS_NO_NULLPTR. Đây có vẻ như là một cách giải quyết nỗ lực tốt nhất khi trình biên dịch không cung cấp nullptr.
chris

5
@Fullfungo Tiêu chuẩn nói rằng đó nullptr_tlà một loại cơ bản. Việc thực hiện nó như một kiểu lớp không tạo ra sự thực hiện phù hợp. Xem bình luận của chris.
LF

1
@LF Tiêu chuẩn về mặt kỹ thuật có yêu cầu loại cơ bản không phải là loại lớp không?
eerorika

2
@eerorika: is_classis_null_pointercả hai không thể đúng cho cùng một loại. Chỉ một trong các hàm danh mục loại chính có thể trả về true cho một loại cụ thể.
Nicol Bolas

Câu trả lời:


20

Tôi tò mò muốn biết nullptr hoạt động như thế nào.

Nó hoạt động theo cách đơn giản nhất có thể: bởi fiat . Nó hoạt động vì tiêu chuẩn C ++ nói rằng nó hoạt động và nó hoạt động theo cách đó bởi vì tiêu chuẩn C ++ nói rằng việc triển khai phải làm cho nó hoạt động theo kiểu đó.

Điều quan trọng là phải nhận ra rằng không thể thực hiện std::nullptr_tbằng cách sử dụng các quy tắc của ngôn ngữ C ++. Chuyển đổi từ hằng số con trỏ null thành loại std::nullptr_tcon trỏ không phải là chuyển đổi do người dùng xác định. Điều đó có nghĩa là bạn có thể chuyển từ hằng số con trỏ null sang con trỏ, sau đó thông qua chuyển đổi do người dùng xác định sang một loại khác, tất cả trong một chuỗi chuyển đổi ngầm định.

Điều đó là không thể nếu bạn thực hiện nullptr_tnhư một lớp học. Toán tử chuyển đổi biểu thị các chuyển đổi do người dùng xác định và các quy tắc trình tự chuyển đổi ngầm định của C ++ không cho phép nhiều hơn một chuyển đổi do người dùng xác định trong chuỗi đó.

Vì vậy, mã bạn đã đăng là một xấp xỉ tốt đẹp std::nullptr_t, nhưng nó không có gì hơn thế. Nó không phải là một thực hiện hợp pháp của các loại. Đây có lẽ là từ một phiên bản cũ hơn của trình biên dịch (còn lại vì lý do tương thích ngược) trước khi trình biên dịch cung cấp hỗ trợ thích hợp cho std::nullptr_t. Bạn có thể thấy điều này bởi thực tế là nó #defines nullptr, trong khi C ++ 11 nói rằng nullptrlà một từ khóa , không phải là một vĩ mô.

C ++ không thể thực hiện std::nullptr_t, giống như C ++ không thể thực hiện inthoặc void*. Chỉ có việc thực hiện có thể thực hiện những điều đó. Đây là những gì làm cho nó một "loại cơ bản"; đó là một phần của ngôn ngữ .


giá trị của nó có thể được chuyển đổi thành loại tích phân giống hệt (void *) 0, nhưng không ngược lại;

Không có chuyển đổi ngầm định từ hằng con trỏ null sang các kiểu tích phân. Có một chuyển đổi từ 0một loại tích phân, nhưng đó là bởi vì đó là số nguyên bằng không, đó là ... một số nguyên.

nullptr_tcó thể được đúc để một kiểu số nguyên (thông qua reinterpret_cast), nhưng nó chỉ có thể được ngầm chuyển đổi sang con trỏ và bool.


4
@Wyck: " fiat "
Nicol Bolas

"Không thể thực hiện std :: nullptr_t bằng cách sử dụng các quy tắc của ngôn ngữ C ++" là gì? Điều đó có nghĩa là trình biên dịch C ++ không thể được viết hoàn toàn bằng chính C ++ (tôi đoán là không)?
miền Bắc

3
@northerner: Ý tôi là bạn không thể viết một loại chính xác tương đương với hành vi cần có std::nullptr_t. Cũng như bạn không thể viết một loại chính xác tương đương với hành vi cần thiết int. Bạn có thể đến gần, nhưng vẫn sẽ có sự khác biệt đáng kể. Và tôi không nói về các máy phát hiện đặc điểm như thế is_classcho thấy loại của bạn là do người dùng định nghĩa. Có những điều về hành vi cần thiết của các loại cơ bản mà bạn đơn giản không thể sao chép bằng cách sử dụng các quy tắc của ngôn ngữ.
Nicol Bolas

1
Chỉ là một từ ngữ ngụy biện. Khi bạn nói "C ++ không thể thực hiện nullptr_t", bạn nói quá rộng. Và nói "chỉ có việc thực hiện mới có thể thực hiện được" chỉ gây nhầm lẫn vấn đề. Ý bạn là nullptr_tkhông thể thực hiện được " trong thư viện C ++ vì đó là một phần của ngôn ngữ cơ sở.
Spencer

1
@Spencer: Không, ý tôi là chính xác những gì tôi đã nói: C ++ ngôn ngữ không thể được sử dụng để thực hiện một loại thực hiện mọi thứ std::nullptr_tbắt buộc phải làm. Giống như C ++, ngôn ngữ không thể thực hiện một kiểu thực hiện mọi thứ intbắt buộc phải làm.
Nicol Bolas
Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.