2013-03-04 31 views
33

Trong C++ dự thảo tiêu chuẩn (N3485), nó khẳng định như sau:Tại sao std :: operator_ptr operator * throw và operator-> không ném?

20.7.1.2.4 quan sát unique_ptr [unique.ptr.single.observers]

typename add_lvalue_reference<T>::type operator*() const; 

1 Requires: get() != nullptr. 
2 Returns: *get(). 

pointer operator->() const noexcept; 

3 Requires: get() != nullptr. 
4 Returns: get(). 
5 Note: use typically requires that T be a complete type. 

Bạn có thể thấy rằng operator* (dereference) không được chỉ định là noexcept, có thể do nó có thể gây ra một segfault, nhưng sau đó operator-> trên cùng một đối tượng được chỉ định là noexcept. Các yêu cầu cho cả hai đều giống nhau, tuy nhiên có một sự khác biệt trong đặc tả ngoại lệ.

Tôi nhận thấy họ có các loại trả về khác nhau, một trả về một con trỏ và một tham chiếu khác. Có phải đó là nói rằng operator-> không thực sự dereference bất cứ điều gì?

Thực tế của vấn đề là sử dụng operator-> trên một con trỏ thuộc bất kỳ loại nào là NULL, sẽ segfault (là UB). Tại sao sau đó, một trong số các giá trị này được chỉ định là noexcept và một số khác thì không?

Tôi chắc chắn tôi đã bỏ qua điều gì đó.

EDIT:

Nhìn vào std::shared_ptr chúng tôi có điều này:

20.7.2.2.5 shared_ptr quan sát [util.smartptr.shared.obs]

T& operator*() const noexcept; 

T* operator->() const noexcept; 

Đó là không giống nhau ? Điều đó có liên quan gì đến các ngữ nghĩa quyền sở hữu khác nhau không?

+4

"Có phải điều đó nói rằng toán tử-> thực sự không coi trọng bất cứ điều gì?" Có, đó là nó –

+0

Có lẽ nó cho phép triển khai để ném vào một con trỏ null dereference nếu họ chọn. – Dan

Trả lời

25

Một segfault nằm ngoài hệ thống ngoại lệ của C++. Nếu bạn dereference một con trỏ null, bạn không nhận được bất kỳ loại ngoại lệ ném (tốt, ít nhất nếu bạn thực hiện theo các điều khoản Require:; xem dưới đây để biết chi tiết).

Đối với operator->, thường được triển khai đơn giản là return m_ptr; (hoặc return get(); cho unique_ptr).Như bạn có thể thấy, chính người vận hành không thể ném - nó chỉ trả về con trỏ. Không có dereferencing, không có gì. Ngôn ngữ có một số quy tắc đặc biệt cho p->identifier:

§13.5.6 [over.ref] p1

Một biểu x->m được hiểu như là (x.operator->())->m cho một đối tượng lớp x loại T nếu T::operator->() tồn tại và nếu các nhà điều hành được chọn như chức năng phù hợp nhất bởi cơ chế giải quyết quá tải (13.3).

Ở trên áp dụng đệ quy và cuối cùng phải tạo con trỏ, được sử dụng operator->. Điều này cho phép người dùng con trỏ thông minh và trình vòng lặp đơn giản thực hiện smart->fun() mà không phải lo lắng về bất kỳ điều gì.

Lưu ý cho các phần Require: của đặc điểm kỹ thuật: Những điều kiện tiên quyết này biểu thị. Nếu bạn không gặp họ, bạn đang gọi UB.

Tại sao sau đó, một trong những điều này được chỉ định là ngoại lệ và thẻ còn lại không được xác định?

Thành thật mà nói, tôi không chắc chắn. Dường như dereferencing là con trỏ phải luôn là noexcept, tuy nhiên, unique_ptr cho phép bạn hoàn toàn thay đổi loại con trỏ bên trong là gì (thông qua dấu phân cách). Bây giờ, với tư cách là người dùng, bạn có thể xác định ngữ nghĩa hoàn toàn khác nhau cho operator* trên loại pointer của mình. Có lẽ nó tính toán mọi thứ trên bay? Tất cả những thứ thú vị, có thể ném.


Nhìn vào std :: shared_ptr chúng tôi có điều này:

này rất dễ dàng để giải thích - shared_ptr không hỗ trợ tùy biến nêu trên cho các loại con trỏ, có nghĩa là xây dựng -in ngữ nghĩa luôn áp dụng áp dụng - và *p trong đó pT* chỉ đơn giản là không ném.

+4

+1 cho khả năng ngữ nghĩa tùy chỉnh của' toán tử * '. – Angew

+0

Bạn có thể thay đổi loại con trỏ bên trong, nhưng tôi không nghĩ đó là do sự cố. Con trỏ là "' std :: remove_reference :: type :: pointer' nếu kiểu đó tồn tại, nếu không T * " –

+0

Có lẽ bạn đang đúng về khả năng' operator * 'overload. –

-2

Về:

Có phải đó là nói rằng nhà khai thác> không thực sự dereference bất cứ điều gì?

Không, việc đánh giá tiêu chuẩn của -> cho một loại quá tải operator-> là:

a->b; // (a.operator->())->b 

Tức là việc đánh giá được định nghĩa đệ quy, khi mã nguồn chứa một ->, operator-> được áp dụng có lãi suất khác biểu hiện với một -> rằng bản thân có thể tham khảo một operator-> ...

Về câu hỏi tổng quát, nếu con trỏ là null, hành vi không xác định và thiếu noexcept cho phép triển khai throw. Nếu chữ ký là noexcept thì việc triển khai có thể không throw (số throw sẽ là cuộc gọi đến std::terminate).

+1

Vì vậy, loại của '(a.operator ->())' là gì? –

+0

Có hành vi không xác định đã cho phép thực hiện để ném, ngay cả khi chức năng là 'noexcept'. –

+0

@TonyTheLion: Dù bạn định nghĩa nó là gì ... nó có thể sinh ra một con trỏ hoặc một đối tượng, nếu nó mang một đối tượng, 'toán tử->' sẽ được gọi trên đó để 'a-> b' được dịch sang' ((a.operator ->()) -> toán tử ->()) b', trừ khi đánh giá thứ hai cũng mang lại một con trỏ không trong trường hợp đó ... –

1

Thẳng thắn, điều này trông giống như một khiếm khuyết đối với tôi. Về mặt khái niệm, a-> b nên luôn luôn tương đương với (* a) .b, và điều này áp dụng ngay cả khi một là một con trỏ thông minh. Nhưng nếu * a không phải là ngoại lệ, thì (* a) .b không phải, và do đó a-> b không nên.

+0

http://open-std.org/jtc1/sc22 /wg21/docs/lwg-active.html#2337 có liên quan. Nó có khả năng bị đóng như NAD, điều này sẽ không làm cho các thành viên 'unique_ptr' nhất quán. –

4

Đối với những gì nó đáng giá, đây là một chút về lịch sử và cách mọi thứ diễn ra như bây giờ.

Trước N3025, operator * không được chỉ định với noexcept, nhưng mô tả của nó chứa Throws: nothing. Yêu cầu này đã được gỡ bỏ trong N3025:

Thay đổi [unique.ptr.single.observers] như đã nêu (834) [Để biết chi tiết xem các chú thích phần]:

typename add_lvalue_reference<T>::type operator*() const;
1 - Yêu cầu: get() !=nullptr.
2 - Lợi nhuận: *get().
3 - Ném: không có gì.

Dưới đây là nội dung của "Ghi chú" phần đã nói ở trên:

Trong đánh giá của giấy này nó đã trở thành tranh cãi làm thế nào để xác định đúng ngữ nghĩa hoạt động của nhà điều hành *, operator [], và hàm so sánh dị. [structure.specifications]/3 không nói rõ liệu một phần tử Returns (trong trường hợp không có tương đương mới với công thức) chỉ định các hiệu ứng. Hơn nữa nó không rõ liệu điều này sẽ cho phép biểu thức trả về như vậy để thoát ra thông qua một ngoại lệ, nếu thêm một Throws: -Nothing element được cung cấp (người triển khai sẽ được yêu cầu bắt những cái đó không?). Để giải quyết xung đột này, bất kỳ phần tử Ném hiện có nào đã bị loại bỏ cho các hoạt động này, ít nhất là phù hợp với [unique.ptr.special] và các phần khác của tiêu chuẩn. Kết quả của việc này là chúng tôi cung cấp hỗ trợ ngầm cho khả năng ném các hàm so sánh, nhưng không phải cho hàm đồng nhất == và! =, Điều này có thể gây ngạc nhiên một chút.

Các giấy tương tự cũng chứa một đề nghị để chỉnh sửa các định nghĩa của operator ->, nhưng nó đọc như sau:

pointer operator->() const;
4 - Yêu cầu: get() != nullptr.
5 - Lợi nhuận: get().
6 - Ném: không có gì.
7 - Lưu ý: sử dụng thường yêu cầu T phải là một loại hoàn chỉnh.

Theo như câu hỏi của chính nó đi: nó đi đến một sự khác biệt cơ bản giữa chính người vận hành và biểu thức mà toán tử được sử dụng.

Khi bạn sử dụng operator*, toán tử sẽ hủy tham chiếu con trỏ, có thể ném.

Khi bạn sử dụng operator->, chính nhà khai thác chỉ trả về một con trỏ (không được phép ném). Con trỏ đó sau đó được tham chiếu trong biểu thức có chứa ->. Bất kỳ ngoại lệ nào từ dereferencing con trỏ sẽ xảy ra trong biểu thức xung quanh thay vì trong chính toán tử.

Các vấn đề liên quan