2012-11-07 32 views
5

Trong chức năng sau, tôi thử xem chuỗi s có thể chuyển đổi thành loại T hay không bằng cách xem liệu tôi có thể đọc loại T hay không, và nếu đầu vào được tiêu thụ hoàn toàn sau đó. Tôi muốnLàm cách nào để kiểm tra việc trích xuất luồng đã tiêu thụ tất cả đầu vào?

template <class T> 
bool can_be_converted_to(const std::string& s, T& t) 
{ 
    std::istringstream i(s); 
    i>>std::boolalpha; 
    i>>t; 
    if (i and i.eof()) 
    return true; 
    else 
    return false; 
} 

Tuy nhiên, can_be_converted_to<bool>("true") là false, vì i.eof() là sai ở phần cuối của hàm.

Điều này là chính xác, mặc dù chức năng đã đọc toàn bộ chuỗi, bởi vì nó chưa cố gắng đọc qua kết thúc chuỗi. (Vì vậy, rõ ràng chức năng này làm việc cho int và double vì istringstream đọc qua cuối cùng khi đọc này.)

Vì vậy, giả định rằng tôi thực sự cần phải được kiểm tra (i and <input completely consumed>):

Q: Làm thế nào để kiểm tra xem các đầu vào đã hoàn toàn tiêu thụ w/o bằng cách sử dụng eof()?

+0

Không phải là câu trả lời, chỉ cần lưu ý: xem xét sử dụng biến tmp của loại 'T' khi bạn ghi đè' t' ngay cả trong trường hợp 'e.eof()' là sai. – Mene

+0

Bạn có nhận được 'EOF' trả về nếu bạn làm' i.peek() ' –

Trả lời

8

Sử dụng peek() hoặc get() để kiểm tra những gì sẽ là kế tiếp trong luồng:

return (i >> std::boolalpha >> t && i.peek() == EOF); 

Phiên bản của bạn không làm việc cho số nguyên, một trong hai. Xem xét đầu vào này: 123 45. Nó sẽ đọc 123 và báo cáo đúng, mặc dù vẫn còn một số ký tự còn lại trong luồng.

3

Trong nhiều triển khai của thư viện chuẩn, eof sẽ chỉ được đặt sau khi bạn thử đọc ngoài phần cuối. Bạn có thể xác minh rằng trong mã của bạn bằng cách thực hiện:

char _; 
if (i && !(i >> _)) { // i is in a valid state, but 
         // reading a single extra char fails 
3

Mở rộng về câu trả lời jrok, bạn có thể sử dụng i.get() chỉ dễ dàng như i.peek(), ít nhất là trong trường hợp này. (Tôi không biết nếu có bất kỳ lý do nào là để thích một người khác.)

Ngoài ra, theo quy ước rằng không gian trắng không bao giờ là bất cứ thứ gì trừ bộ tách , bạn có thể muốn tách nó ra trước khi kiểm tra kết thúc. Cái gì như:

return i >> std::ws && i.get() == std::istream::traits_type::eof(); 

Triển khai một số cũ của std::ws là lỗi, và sẽ đưa dòng trong tình trạng lỗi. Trong trường hợp đó, bạn phải đảo ngược vùng kiểm tra, và làm điều gì đó như:

return !(i >> std::ws) || i.get() == std::istream::traits_type::eof(); 

Hoặc chỉ cần đọc std::ws trước tình trạng này, và phụ thuộc duy nhất vào các i.get().

(Tôi không biết nếu lỗi std::ws vẫn là một vấn đề. Tôi đã phát triển một phiên bản của nó mà làm việc trở lại khi nó đã được, và tôi vừa tiếp tục sử dụng nó.)

2

tôi sẽ muốn cung cấp một cách tiếp cận hoàn toàn khác: Lấy chuỗi đầu vào của bạn, tự mình mã hóa và sau đó chuyển đổi các trường riêng lẻ bằng cách sử dụng boost::lexical_cast<T>.

Lý do: Tôi lãng phí một buổi chiều về phân tích cú pháp một chuỗi có chứa 2 trường int và 2 trường đôi, được phân tách bằng dấu cách. Làm những điều sau đây:

int i, j; 
double x, y; 
std::istringstream ins{str}; 

ins >> i >> j >> x >> y; 
// how to check errors???... 

phân tích đầu vào đúng như

`"5 3 9.9e+01 5.5e+02"` 

một cách chính xác, nhưng không phát hiện ra vấn đề với điều này:

`"5 9.6e+01 5.5e+02"` 

gì xảy ra là i sẽ được thiết lập đến 5 (OK), j sẽ được đặt thành 9 (??), x đến 6.0 (= 0,6e + 01), y đến 550 (OK). Tôi đã khá ngạc nhiên khi thấy failbit không được đặt ... (thông tin nền tảng: OS X 10.9, Apple Clang ++ 6.0, chế độ C++ 11). Tất nhiên bạn có thể nói bây giờ, "Nhưng chờ đợi, các tiêu chuẩn nói rằng nó nên như vậy", và bạn có thể đúng, nhưng biết rằng nó là một tính năng chứ không phải là một lỗi không làm giảm đau nếu bạn muốn để làm kiểm tra lỗi thích hợp mà không cần viết dặm mã.

OTOH, nếu bạn sử dụng "Marius" xuất sắc tokeniser function và tách str trước trên khoảng trắng thì đột nhiên mọi thứ trở nên rất dễ dàng. Đây là một phiên bản sửa đổi nhỏ của tokeniser. Tôi viết lại nó để trả về một vectơ dây; bản gốc là một mẫu đặt các mã thông báo trong một vùng chứa với các phần tử có thể chuyển đổi thành chuỗi. (Đối với những người cần một cách tiếp cận chung chung như vậy xin vui lòng tham khảo link gốc ở trên.)

// \param str: the input string to be tokenized 
// \param delimiters: string of delimiter characters 
// \param trimEmpty: if true then empty tokens will be trimmed 
// \return a vector of strings containing the tokens 
std::vector<std::string> tokenizer(
    const std::string& str, 
    const std::string& delimiters = " ", 
    const bool trimEmpty = false 
) { 
    std::vector<std::string> tokens; 
    std::string::size_type pos, lastPos = 0; 
    const char* strdata = str.data(); 
    while(true) { 
     pos = str.find_first_of(delimiters, lastPos); 
     if(pos == std::string::npos) { 
      // no more delimiters 
      pos = str.length(); 
      if(pos != lastPos || !trimEmpty) { 
       tokens.emplace_back(strdata + lastPos, pos - lastPos); 
      } 
      break; 
     } else { 
      if(pos != lastPos || !trimEmpty) { 
       tokens.emplace_back(strdata + lastPos, pos - lastPos); 
      } 
     } 
     lastPos = pos + 1; 
    } 
    return tokens; 
} 

và sau đó chỉ cần sử dụng nó như thế này (ParseError là một số đối tượng ngoại lệ):

std::vector<std::string> tokens = tokenizer(str, " \t", true); 
if (tokens.size() < 4) 
    throw ParseError{"Too few fields in " + str}; 

try { 
    unsigned int i{ boost::lexical_cast<unsigned int>(tokens[0]) }, 
     j{ boost::lexical_cast<unsigned int>(tokens[1]) }; 
    double x{ boost::lexical_cast<double>(tokens[2]) }, 
     y{ boost::lexical_cast<double>(tokens[3]) }; 
    // print or process i, j, x, y ... 
} catch(const boost::bad_lexical_cast& error) { 
    throw ParseError{"Could not parse " + str}; 
} 

Lưu ý: bạn có thể sử dụng Boost split hoặc tokenizer nếu bạn muốn, nhưng chúng chậm hơn so với tokeneller của Marius (ít nhất là trong môi trường của tôi).

Cập nhật: Thay vì boost::lexical_cast<T> bạn có thể sử dụng các hàm C++ 11 "std::sto*" (ví dụ: stoi để chuyển đổi mã thông báo chuỗi thành int). Chúng ném hai loại ngoại lệ: std::invalid_argument nếu chuyển đổi không thể được thực hiện và std::out_of_range nếu giá trị được chuyển đổi không thể được biểu diễn. Bạn có thể nắm bắt chúng một cách riêng biệt hoặc cha mẹ của chúng std::runtime_error. Các sửa đổi đối với mã ví dụ ở trên được để lại dưới dạng bài tập cho người đọc :-)

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