Xây dựng các ngoại lệ tiêu chuẩn với đối số con trỏ null và các điều kiện hậu kỳ không thể


9

Hãy xem xét chương trình sau:

#include<stdexcept>
#include<iostream>

int main() {
    try {
        throw std::range_error(nullptr);
    } catch(const std::range_error&) {
        std::cout << "Caught!\n";
    }
}

GCC và Clang với libstdc ++ gọi std::terminatevà hủy bỏ chương trình với thông báo

terminate called after throwing an instance of 'std::logic_error'
  what():  basic_string::_S_construct null not valid

Clang với libc ++ segfaults về xây dựng ngoại lệ.

Xem thần thánh .

Là các trình biên dịch hành xử phù hợp tiêu chuẩn? Phần có liên quan của tiêu chuẩn [chẩn đoán.range.error ] (C ++ 17 N4659) có nói rằng std::range_errorconst char*quá tải hàm tạo nên được ưu tiên hơn const std::string&quá tải. Phần này cũng không nêu bất kỳ điều kiện tiên quyết nào trên hàm tạo và chỉ nêu phần hậu điều kiện

Hậu điều : strcmp(what(), what_­arg) == 0.

Postcondition này luôn có hành vi không xác định nếu what_arglà con trỏ null, vì vậy điều này có nghĩa là chương trình của tôi cũng có hành vi không xác định và cả hai trình biên dịch đều tuân thủ? Nếu không, làm thế nào người ta nên đọc các điều kiện không thể như vậy trong tiêu chuẩn?


Về ý nghĩ thứ hai, tôi nghĩ nó phải có nghĩa là hành vi không xác định cho chương trình của tôi, bởi vì nếu nó không (thì hợp lệ) con trỏ không trỏ đến chuỗi kết thúc null cũng sẽ được cho phép, điều này rõ ràng là vô nghĩa.

Vì vậy, giả sử đó là sự thật, tôi muốn tập trung vào câu hỏi nhiều hơn về cách tiêu chuẩn ngụ ý hành vi không xác định này. Liệu nó xuất phát từ sự bất khả thi của postcondation rằng cuộc gọi cũng có hành vi không xác định hoặc là điều kiện tiên quyết bị lãng quên?


Lấy cảm hứng từ câu hỏi này .


Nghe có vẻ như std :: Range_error được phép lưu trữ mọi thứ bằng cách tham khảo, vì vậy tôi sẽ không ngạc nhiên. Gọi what()khi nullptrđược thông qua có thể sẽ gây ra vấn đề.
Chipster

@Chipster Tôi không chắc chính xác ý bạn là gì, nhưng sau khi nghĩ lại về điều này, tôi nghĩ đó phải là hành vi không xác định. Tôi đã chỉnh sửa câu hỏi của mình để tập trung vào câu hỏi nhiều hơn về cách diễn đạt tiêu chuẩn ngụ ý hành vi không xác định.
quả óc chó

Nếu nullptrđược thông qua, tôi sẽ nghĩ rằng what()sẽ phải hủy bỏ nó vào một lúc nào đó để có được giá trị. Đó sẽ là cuộc hội thảo a nullptr, đó là vấn đề tốt nhất và chắc chắn để sụp đổ là tồi tệ nhất.
Chipster

Tôi đồng ý, mặc dù. Nó phải là hành vi không xác định. Tuy nhiên, đưa điều đó vào một câu trả lời đầy đủ giải thích tại sao vượt quá kỹ năng của tôi.
Chipster

Tôi nghĩ rằng đó là một điều kiện tiên quyết rằng đối số trỏ đến một chuỗi C hợp lệ, vì strcmpđược sử dụng để mô tả giá trị của what_arg. Đó là những gì phần có liên quan từ tiêu chuẩn C nói dù sao, được đề cập bởi đặc điểm kỹ thuật của <cstring>. Tất nhiên các từ ngữ có thể rõ ràng hơn.
LF

Câu trả lời:


0

Từ tài liệu :

Vì sao chép std :: Range_error không được phép đưa ra ngoại lệ, thông báo này thường được lưu trữ bên trong dưới dạng một chuỗi được tính tham chiếu được phân bổ riêng. Đây cũng là lý do tại sao không có nhà xây dựng nào lấy std :: string &&: nó sẽ phải sao chép nội dung.

Điều này cho thấy lý do tại sao bạn nhận được segfault, api thực sự coi nó như một chuỗi thực sự. Nói chung trong cpp nếu một cái gì đó là tùy chọn, sẽ có một hàm tạo / hàm quá tải không lấy những gì nó không cần. Vì vậy, việc truyền nullptrcho một hàm không ghi lại một cái gì đó là tùy chọn sẽ là hành vi không xác định. Thông thường, API không lấy con trỏ ngoại trừ chuỗi C. Vì vậy, IMHO an toàn khi giả sử truyền nullptr cho một hàm mong đợi a const char *, sẽ là hành vi không xác định. API mới hơn có thể thích std::string_viewcho những trường hợp đó.

Cập nhật:

Thông thường, thật công bằng khi giả sử API C ++ lấy một con trỏ để chấp nhận NULL. Tuy nhiên, chuỗi C là một trường hợp đặc biệt. Cho đến khi std::string_view, không có cách nào tốt hơn để vượt qua chúng một cách hiệu quả. Nói chung để chấp nhận API const char *, giả định là nó phải là một chuỗi C hợp lệ. tức là một con trỏ tới một chuỗi chars kết thúc bằng '\ 0'.

range_errorcó thể xác thực rằng con trỏ không nullptrnhưng nó không thể xác thực nếu nó kết thúc bằng '\ 0'. Vì vậy, tốt hơn là không làm bất kỳ xác nhận.

Tôi không biết từ ngữ chính xác trong tiêu chuẩn, nhưng điều kiện tiên quyết này có thể được giả định tự động.


-2

Điều này quay trở lại câu hỏi cơ bản là nó có ổn không khi tạo chuỗi std :: từ nullptr? và nó nên làm gì?

www.cplusplus.com nói

Nếu s là một con trỏ rỗng, nếu n == npose hoặc nếu phạm vi được chỉ định bởi [đầu tiên, cuối cùng) không hợp lệ, nó sẽ gây ra hành vi không xác định.

Vậy khi nào

throw std::range_error(nullptr);

được gọi là việc thực hiện cố gắng làm một cái gì đó như

store = std::make_shared<std::string>(nullptr);

mà không xác định. Mà tôi sẽ xem xét một lỗi (mà không cần đọc từ ngữ thực tế trong tiêu chuẩn). Thay vào đó, các nhà phát triển tự do có thể đã làm một cái gì đó như

if (what_arg)
  store = std::make_shared<std::string>(nullptr);

Nhưng sau đó người bắt phải kiểm tra nullptr what();hoặc nó sẽ bị sập ở đó. Vì vậy, std::range_errornên gán một chuỗi rỗng hoặc "(nullptr)" như một số ngôn ngữ khác.


Điều này quay trở lại câu hỏi cơ bản là có ổn không khi tạo chuỗi std :: từ nullptr? - Tôi không nghĩ thế.
Konrad Rudolph

Tôi không nghĩ rằng tiêu chuẩn chỉ định bất cứ nơi nào ngoại lệ phải lưu a std::stringvà nhà std::stringxây dựng không nên được chọn bởi độ phân giải quá tải.
quả óc chó

Quá const char *tải được chọn theo độ phân giải quá tải
MM
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.