2015-03-18 17 views
14

Trong cơ sở mã của chúng tôi chúng tôi có nhiều công trình xây dựng như thế này:Safe con trỏ dereferencing trong C++

auto* pObj = getObjectThatMayVeryRarelyBeNull(); 
if (!pObj) throw std::runtime_error("Ooops!"); 
// Use pObj->(...) 

Trong 99,99% các trường hợp việc kiểm tra này không được kích hoạt. Tôi đang suy nghĩ về các giải pháp sau đây:

auto& obj = deref_or_throw(getObjectThatMayVeryRarelyBeNull()); 
// Use obj.(...) 

đâu deref_or_throw được khai báo như sau:

template<class T> T& deref_or_throw(T* p) { 
    if (p == nullptr) { throw std::invalid_argument("Argument is null!"); } 
    return *p; 
} 

đang Đó là rõ ràng hơn nhiều và hoạt động như tôi cần.

Câu hỏi đặt ra là: tôi có đang phát minh lại bánh xe không? Có một số giải pháp liên quan trong tiêu chuẩn hoặc tăng cường? Hay bạn có một số ý kiến ​​về giải pháp?

PS. Câu hỏi liên quan (không có câu trả lời thỏa mãn): Is there a C++ equivalent of a NullPointerException

+0

Có mã nào thực sự giao dịch với trường hợp 'nullptr' không? Ví dụ. bất cứ thứ gì như 'if (! obj) {std :: cout <<" Null, nhưng vẫn ổn, hãy tiếp tục với một cái gì đó có ý nghĩa \ n "; } else {std :: cout << "Không-Null, đang làm điều gì khác \ n"; } '. Hoặc là trường hợp 'nullptr' luôn luôn là một" lối ra, bởi vì đây là sai "-path? – stefan

+0

Mã này tồn tại, nhưng rất hiếm. Trong hầu hết các trường hợp, chúng tôi không thể tiếp tục. Không có gì chúng tôi có thể làm. Toàn bộ thói quen là vô nghĩa. Vì vậy, có thể sự tồn tại của đối tượng này là _precondition_? Và tôi cần phải kiểm tra nó và sau đó dereference bằng tay con trỏ mà không đáng lo ngại? – Mikhail

+1

Chính xác đây là những gì bạn cần phải làm rõ cho chính mình, imho. Nếu đó là điều kiện tiên quyết, có lẽ một 'khẳng định' là phù hợp hơn. Nếu không, đây rõ ràng là một điều đặc biệt (0,01% là khá đặc biệt), do đó, một 'ngoại lệ' là thích hợp. Afaik, không có thứ gì trong thư viện. – stefan

Trả lời

4

Có hai cách xử lý sự cố "hiếm khi xảy ra lỗi". Đầu tiên, đó là giải pháp ngoại lệ được đề xuất. Đối với điều này, phương pháp deref_or_throw là một điều tốt, mặc dù tôi muốn ném một số runtime_error, thay vì một ngoại lệ invalid_argument. Bạn cũng có thể xem xét đặt tên nó là NullPointerException, nếu bạn thích điều đó.

Tuy nhiên, nếu trường hợp ptr != nullptr thực sự là điều kiện tiên quyết cho thuật toán, bạn nên cố gắng hết sức để đạt được an toàn 100% trong trường hợp không có giá trị này. An assert là điều thích hợp trong trường hợp này.

Ưu điểm của assert:

  • Không có chi phí khi chạy ở chế độ phát hành
  • Làm cho nó tinh thể rõ ràng đối với các nhà phát triển, rằng ông chịu trách nhiệm cho việc này không bao giờ xảy ra.

Nhược điểm của assert:

  • tiềm năng hành vi undefined trong chế độ phát hành

Bạn cũng nên xem xét viết một phương pháp getObjectThatMayNeverBeNullButDoingTheSameAsGetObjectThatMayVeryRarelyBeNull(): Một phương pháp được đảm bảo để trả về một con trỏ null không để một đối tượng khác hoàn toàn tương đương với phương thức ban đầu của bạn. Tuy nhiên, nếu bạn có thể đi thêm dặm, tôi mạnh mẽ đề nghị để không trở về một con trỏ nguyên anyway. Embrace C++ 11 và trả về các đối tượng theo giá trị :-)

+0

Tôi muốn có các xác nhận, không được xóa trong bản phát hành. Và tôi muốn phân biệt giữa các xác nhận và điều kiện tiên quyết. Bất kỳ liên kết tốt nào về chủ đề đó? – Mikhail

+0

Các xác nhận là điều kiện tiên quyết (hoặc ít nhất là điều gần nhất với nó). Nhưng không có gì ngăn cản bạn sử dụng cả xác nhận và ngoại lệ. :) – stefan

+0

Vâng, các giải pháp rất rõ ràng, rằng tôi chắc chắn ai đó đã làm tốt công việc đó và có một khuôn khổ nhất quán và chung chung về nó. Mặc dù tôi không biết. Ví dụ, Boost.assert không có điều kiện tiên quyết. – Mikhail

1

Bạn có thể, và có lẽ, nên thiết kế với null trường hợp cần lưu ý. Ví dụ: trong miền sự cố của bạn, khi nào phương thức lớp học trở nên có ý nghĩa để trả về null? Khi nào nó có ý nghĩa khi gọi hàm với các đối số null?

Nói chung, có thể hữu ích khi xóa tài liệu tham khảo null khỏi mã bất cứ khi nào có thể. Nói cách khác, bạn có thể lập trình cho bất biến rằng "điều này sẽ không rỗng ở đây ... hoặc ở đó". Điều này có nhiều lợi ích. Bạn sẽ không phải obfuscate mã bằng cách gói các phương thức trong deref_or_throw. Bạn có thể đạt được ý nghĩa ngữ nghĩa hơn từ mã, bởi vì, thường là những thứ null trong thế giới thực? Mã của bạn có thể dễ đọc hơn nếu bạn sử dụng ngoại lệ để chỉ ra lỗi thay vì giá trị null. Và cuối cùng, bạn giảm nhu cầu kiểm tra lỗi và cũng giảm nguy cơ lỗi thời gian chạy (dereed null pointer dereference).

Nếu hệ thống của bạn không được thiết kế với null trường hợp trong tâm trí, tôi muốn nói đó là tốt nhất để lại nó một mình và không điên gói tất cả mọi thứ với deref_or_throw. Có lẽ lấy một cách tiếp cận Agile. Khi bạn đang viết mã, kiểm tra các hợp đồng mà đối tượng lớp của bạn cung cấp dưới dạng dịch vụ. Bao nhiêu hợp đồng này có thể được kỳ vọng một cách hợp lý để trả lại null? Giá trị ngữ nghĩa của null trong những trường hợp này là gì?

Bạn có thể xác định các lớp học trong hệ thống của mình có thể được mong đợi một cách hợp lý để trả về null. Đối với các lớp này, null có thể là logic nghiệp vụ hợp lệ, thay vì các trường hợp rìa có dấu hiệu của lỗi triển khai. Đối với các lớp học có thể được mong đợi để trả lại null, séc bổ sung có thể đáng để đảm bảo an toàn. Theo nghĩa này, việc kiểm tra null sẽ giống như logic nghiệp vụ hơn là chi tiết triển khai ở mức độ thấp. Mặc dù vậy, trên toàn bộ bảng, có hệ thống gói tất cả các tham số con trỏ trong deref_or_throw có thể là một cách chữa trị độc tố của chính nó.

+0

Hàm 'getObjectThatMayVeryRarelyBeNull()' sẽ có khả năng trả về 'null'. Rất hiếm, nhưng điều đó là có thể. Nhưng vì nó là hiếm, hầu hết các thói quen (ngoại trừ một số đặc biệt) mong đợi nó sẽ trả về một đối tượng. Nếu không, chúng đơn giản là vô nghĩa. – Mikhail

+0

Tôi sẽ bị cám dỗ xóa bỏ giá trị null là giá trị trả lại hợp lệ và có thể xem xét một tùy chọn khác để chỉ ra trường hợp rất hiếm. Mà không biết trường hợp sử dụng của bạn, tôi không thể suy đoán chính xác những gì sẽ được. Có vẻ như lớp học của bạn có thể trả về một kết quả "đáng ngạc nhiên" mà không phải tất cả người phụ thuộc sẽ xử lý đúng cách, do đó, có vẻ như thực sự nguy hiểm để trả về null ở đó. Và tất nhiên, các độc giả trong tương lai của mã của bạn không cần phải nhận sự tinh tế rằng 'getObjectThatMayVeryRarelyBeNull()' trả về null 0,01% thời gian –

+0

Đây thực sự là một câu hỏi thiết kế phức tạp, và đó là lý do tại sao tôi không hỏi nó :) Tôi hạn chế khu vực của các giải pháp có thể để giao diện của 'getObjectThatMayVeryRarelyBeNull()' không thể thay đổi. – Mikhail

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