2012-02-02 38 views
15

tôi cố gắng tránh tập trong C++ hoàn toàn. Tức là, tôi chỉ sử dụng các khởi tạo và khai báo các biến cục bộ là const bất cứ khi nào có thể (tức là ngoại trừ các biến vòng lặp hoặc các bộ tích lũy).Bind tạm thời để tham chiếu không const

Bây giờ, tôi đã tìm thấy trường hợp điều này không hiệu quả. Tôi tin rằng đây là một mô hình chung nhưng đặc biệt nó nảy sinh trong tình huống sau đây:

Mô tả vấn đề

Hãy nói rằng tôi có một chương trình mà tải các nội dung của một tập tin đầu vào thành một chuỗi. Bạn có thể gọi công cụ bằng cách cung cấp tên tệp (tool filename) hoặc bằng cách sử dụng luồng đầu vào chuẩn (cat filename | tool). Bây giờ, làm thế nào để tôi khởi tạo chuỗi?

Sau đây không hoạt động:

bool const use_stdin = argc == 1; 
std::string const input = slurp(use_stdin ? static_cast<std::istream&>(std::cin) 
              : std::ifstream(argv[1])); 

Tại sao không làm việc này? Bởi vì nguyên mẫu của slurp cần phải xem xét như sau:

std::string slurp(std::istream&); 

Đó là, đối số i phi-const và kết quả là tôi không thể gắn nó vào một tạm thời. Dường như không có cách nào xung quanh việc này bằng cách sử dụng một biến riêng biệt.

Ugly Cách giải quyết

Tại thời điểm này, tôi sử dụng các giải pháp sau đây:

std::string input; 
if (use_stdin) 
    input = slurp(std::cin); 
else { 
    std::ifstream in(argv[1]); 
    input = slurp(in); 
} 

Nhưng điều này được cọ xát với tôi một cách sai lầm. Trước hết, đó là mã nhiều hơn (trong SLOC) nhưng nó cũng sử dụng một if thay vì biểu thức điều kiện logic (ở đây) hợp lý hơn và nó sử dụng chuyển nhượng sau khi khai báo mà tôi muốn tránh.

Có cách nào tốt để tránh kiểu khởi tạo gián tiếp này không? Sự cố có thể được khái quát hóa cho tất cả các trường hợp bạn cần phải thay đổi đối tượng tạm thời. Không phát trực tiếp theo cách không được thiết kế để đối phó với các trường hợp như vậy (luồng const không có ý nghĩa gì, nhưng vẫn hoạt động trên luồng tạm thời có ý nghĩa)?

+0

Tại sao 'static_cast' là cần thiết ở đây? –

+0

@ n.m .: Trình biên dịch không thể xem qua '?:'. Cả hai mặt của ':' phải cùng loại. –

+0

'slurp' đang làm gì? :) –

Trả lời

14

Tại sao không đơn giản quá tải slurp?

std::string slurp(char const* filename) { 
    std::ifstream in(filename); 
    return slurp(in); 
} 

int main(int argc, char* argv[]) { 
    bool const use_stdin = argc == 1; 
    std::string const input = use_stdin ? slurp(std::cin) : slurp(argv[1]); 
} 

Đây là giải pháp chung với toán tử có điều kiện.

+0

+1 Một giải pháp tuyệt vời. Tôi đang sử dụng nó rất nhiều trong Python hiện nay, nhưng tò mò đủ, nó đã không xảy ra với tôi để làm tương tự trong C + +. –

+0

+1 để đơn giản và dễ hiểu. – vhallac

11

Các giải pháp với if là nhiều hơn hoặc ít hơn các giải pháp tiêu chuẩn khi đối phó với argv:

if (argc == 1) { 
    process(std::cin); 
} else { 
    for (int i = 1; i != argc; ++ i) { 
     std::ifstream in(argv[i]); 
     if (in.is_open()) { 
      process(in); 
     } else { 
      std::cerr << "cannot open " << argv[i] << std::endl; 
    } 
} 

này không xử lý trường hợp của bạn, tuy nhiên, vì mối quan tâm chính của bạn là để có được một chuỗi, không được "xử lý" tên tệp args.

Trong mã của riêng mình, tôi sử dụng MultiFileInputStream mà tôi đã viết, trong đó lấy danh sách tên tệp trong hàm tạo và chỉ trả về EOF khi lần cuối cùng được đọc: nếu danh sách trống std::cin. này cung cấp một giải pháp thanh lịch và đơn giản cho vấn đề của bạn:

MultiFileInputStream in(
     std::vector<std::string>(argv + 1, argv + argc)); 
std::string const input = slurp(in); 

Lớp này có giá trị bằng văn bản, vì nó thường là hữu ích nếu bạn thường ghi chương trình tiện ích Unix-like. Nó chắc chắn không tầm thường, tuy nhiên, và có thể có nhiều công việc nếu đây là nhu cầu một lần.

Một giải pháp tổng quát hơn là dựa trên thực tế là bạn có thể gọi một hàm thành viên không const trên tạm thời, và thực tế là hầu hết các hàm thành viên của std::istream trở lại một std::istream& — một không const tham chiếu sau đó sẽ liên kết với tham chiếu không const. Vì vậy, bạn luôn có thể viết một cái gì đó như:

std::string const input = slurp(
      use_stdin 
      ? std::cin.ignore(0) 
      : std::ifstream(argv[1]).ignore(0)); 

tôi coi đây là một chút của một hack, tuy nhiên, và nó có tổng quát hơn vấn đề mà bạn không thể kiểm tra xem mở (gọi bằng các constructor của std::ifstream làm việc.

Tổng quát hơn, mặc dù tôi hiểu những gì bạn đang cố gắng để đạt được, tôi nghĩ rằng bạn sẽ thấy rằng IO sẽ hầu như luôn luôn đại diện cho một ngoại lệ. bạn không thể đọc một int mà không cần phải đã xác định nó trước tiên và bạn không thể đọc al mà không cần phải xác định trước tiên là std::string. Tôi đồng ý rằng nó không phải là thanh lịch như nó có thể được, nhưng sau đó, mã mà chính xác xử lý lỗi hiếm khi thanh lịch như người ta có thể thích. (Một giải pháp đây sẽ được lấy từ std::ifstream để ném một ngoại lệ nếu mở đã không làm việc;. Tất cả bạn cần là một constructor mà kiểm tra cho is_open() trong cơ thể constructor)

+0

+1 Tôi thích giải pháp 'MultiFileInputStream' tốt nhất. Nếu API luồng không giải quyết được sự cố của bạn, hãy thêm shim ở trên cùng. :) – vhallac

+0

API luồng không giải quyết được sự cố. Bạn chỉ cần mở rộng việc triển khai. ('MultiFileInputStream' kế thừa từ' std :: istream'. Iostreams được thiết kế với phần mở rộng trong đầu, và tôi không thể nghĩ ra một ứng dụng mà chúng ta không có ít nhất một 'streambuf' tùy chỉnh và bất kỳ số lượng các trình điều khiển tùy chỉnh nào.) –

3

Tất cả ngôn ngữ kiểu SSA cần phải có các nút phi thực sự để sử dụng được, thực tế. Bạn sẽ chạy vào cùng một vấn đề trong mọi trường hợp mà bạn cần phải xây dựng từ hai loại khác nhau tùy thuộc vào giá trị của điều kiện. Toán tử bậc ba không thể xử lý các trường hợp như vậy. Tất nhiên, trong C++ 11 có các thủ thuật khác, như di chuyển luồng hoặc tương tự, hoặc sử dụng lambda, và thiết kế của IOstreams hầu như là phản đối chính xác về những gì bạn đang cố gắng làm, theo ý kiến ​​của tôi, bạn sẽ chỉ phải thực hiện một ngoại lệ.

+0

Cảm ơn, tôi không biết tên của vấn đề chung này (dường như tôi cần phải đọc lại cuốn sách Rồng). Một chức năng phi cho iostreams thực sự là những gì tôi cần, và di chuyển có thể là một giải pháp thích hợp. Tuyệt vời, học được điều gì đó thú vị. –

1

Một lựa chọn khác có thể là một biến trung gian để giữ dòng:

std::istream&& is = argc==1? std::move(cin) : std::ifstream(argv[1]); 
std::string const input = slurp(is); 

Lợi dụng thực tế là tên tài liệu tham khảo rvalue là lvalues.

+0

Tôi không thể che giấu xung quanh lý do tại sao điều này là hợp pháp. Làm thế nào để đảm bảo rằng các destructor của 'ifstream' được gọi là ở cuối phạm vi? –

+0

@Konrad: Bạn có quen thuộc với các quy tắc tham chiếu đến const khi ràng buộc tạm thời không? Họ áp dụng ở đây để. Thời gian tồn tại của đối tượng 'ifstream' tạm thời được mở rộng giống như khi nó được liên kết với' std :: istream const & ', và hàm hủy được gọi khi tham chiếu nằm ngoài phạm vi. Lợi thế của việc sử dụng một ref rvalue ở đây là, bạn có thể sửa đổi các đối tượng như bạn vui lòng. – Xeo

+0

Tôi quen thuộc với điều đó. Nhưng nếu điều đó áp dụng ở đây thì sẽ không 'std :: destr' của destructor được gọi hai lần nếu nó bị ràng buộc với' is' trong mã của bạn? Ý tôi là, thông thường nó sẽ không nhưng chính xác loại biểu thức có điều kiện là gì và nó ảnh hưởng như thế nào đến quyết định của trình biên dịch liệu có ràng buộc tuổi thọ của đối tượng với phạm vi 'is' 'không? –

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