Làm cách nào để tạo một dòng c ++ từ bộ mô tả tệp POSIX?


93

Về cơ bản, tôi đang tìm kiếm phiên bản C ++ của fdopen (). Tôi đã nghiên cứu một chút về vấn đề này và đó là một trong những điều tưởng chừng như dễ dàng nhưng hóa ra lại rất phức tạp. Tôi có thiếu điều gì đó trong niềm tin này không (tức là nó thực sự dễ dàng)? Nếu không, có một thư viện tốt ở đâu đó để xử lý việc này?

CHỈNH SỬA: Đã chuyển giải pháp ví dụ của tôi thành một câu trả lời riêng biệt.


@Kazark - bây giờ đã chuyển sang một câu trả lời riêng, cảm ơn.
BD tại Rivenhill

Windows và Linux có thể mmapxử lý tệp và hiển thị nội dung của nó dưới dạng mảng byte.
truthadjustr

Câu trả lời:


72

Từ câu trả lời của Éric Malenfant:

AFAIK, không có cách nào để làm điều này trong C ++ tiêu chuẩn. Tùy thuộc vào nền tảng của bạn, việc triển khai thư viện chuẩn của bạn có thể cung cấp (như một phần mở rộng không chuẩn) một hàm tạo fstream lấy một bộ mô tả tệp làm đầu vào. (Đây là trường hợp của libstdc ++, IIRC) hoặc FILE *.

Dựa trên những quan sát ở trên và nghiên cứu của tôi bên dưới, có mã hoạt động trong hai biến thể; một cho libstdc ++ và một cho Microsoft Visual C ++.


libstdc ++

__gnu_cxx::stdio_filebufmẫu lớp không chuẩn kế thừa std::basic_streambufvà có hàm tạo sau

stdio_filebuf (int __fd, std::ios_base::openmode __mode, size_t __size=static_cast< size_t >(BUFSIZ)) 

với mô tả Hàm khởi tạo này liên kết bộ đệm dòng tệp với bộ mô tả tệp POSIX đang mở.

Chúng tôi tạo nó thông qua xử lý POSIX (dòng 1) và sau đó chúng tôi chuyển nó đến hàm tạo của istream dưới dạng basic_streambuf (dòng 2):

#include <ext/stdio_filebuf.h>
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    ofstream ofs("test.txt");
    ofs << "Writing to a basic_ofstream object..." << endl;
    ofs.close();

    int posix_handle = fileno(::fopen("test.txt", "r"));

    __gnu_cxx::stdio_filebuf<char> filebuf(posix_handle, std::ios::in); // 1
    istream is(&filebuf); // 2

    string line;
    getline(is, line);
    cout << "line: " << line << std::endl;
    return 0;
}

Microsoft Visual C ++

Đã từng có phiên bản không chuẩn của hàm tạo của ifstream lấy bộ mô tả tệp POSIX nhưng nó bị thiếu cả trong tài liệu hiện tại và từ mã. Có một phiên bản không chuẩn khác của phương thức khởi tạo ifstream lấy FILE *

explicit basic_ifstream(_Filet *_File)
    : _Mybase(&_Filebuffer),
        _Filebuffer(_File)
    {   // construct with specified C stream
    }

và nó không được ghi lại (tôi thậm chí không thể tìm thấy bất kỳ tài liệu cũ nào mà nó sẽ có mặt). Chúng tôi gọi nó (dòng 1) với tham số là kết quả của việc gọi _fdopen để lấy C luồng FILE * từ tay cầm tệp POSIX.

#include <cstdio>
#include <iostream>
#include <fstream>
#include <string>

using namespace std;

int main()
{
    ofstream ofs("test.txt");
    ofs << "Writing to a basic_ofstream object..." << endl;
    ofs.close();

    int posix_handle = ::_fileno(::fopen("test.txt", "r"));

    ifstream ifs(::_fdopen(posix_handle, "r")); // 1

    string line;
    getline(ifs, line);
    ifs.close();
    cout << "line: " << line << endl;
    return 0;
}

2
Bây giờ câu trả lời được chấp nhận do tính đầy đủ. Những người khác có thể quan tâm đến giải pháp của tôi bằng cách sử dụng boost, đã được chuyển sang một câu trả lời riêng.
BD tại Rivenhill

1
Đối với linux: Nếu bạn nhìn vào ios_init.cc trong gcc (nguồn tôi có là cho phiên bản 4.1.1) std :: cout được khởi tạo bằng cách khởi tạo stdio_sync_filebuf <char> xung quanh bộ mô tả tệp của bạn, sau đó khởi tạo trên ostream xung quanh stdio_sync_filebuf của bạn < ký tự>. Tôi không thể khẳng định rằng điều này sẽ ổn định.
Lấp lánh

@Sparky Xem xét std::couttriển khai là một ý kiến ​​hay. Tôi tự hỏi sự khác biệt giữa stdio_filebufstdio_sync_filebuf?
Piotr Dobrogost

Các fds POSIX trong MSVC là giả lập. API Windows cho hoạt động tệp khác với POSIX theo nhiều cách - tên hàm khác nhau và kiểu dữ liệu của tham số. Windows nội bộ sử dụng cái gọi là "tay cầm" để xác định các đối tượng API Windows khác nhau và loại API Windows HANDLE được định nghĩa là void *, vì vậy tại tối thiểu nó sẽ không phù hợp với "int" (là 32-bit) trên nền tảng 64-bit. Vì vậy, đối với Windows, bạn có thể quan tâm đến việc tìm kiếm luồng cho phép hoạt động trên Windows API HANDLE.
ivan.ukr

40

AFAIK, không có cách nào để làm điều này trong C ++ tiêu chuẩn. Tùy thuộc vào nền tảng của bạn, việc triển khai thư viện chuẩn của bạn có thể cung cấp (như một phần mở rộng không chuẩn) một hàm tạo fstream lấy một bộ mô tả tệp (Đây là trường hợp cho libstdc ++, IIRC) hoặc một FILE*đầu vào.

Một sự thay thế khác sẽ là sử dụng thiết bị boost :: iostreams :: file_descriptor , mà bạn có thể bọc trong một boost :: iostreams :: stream nếu bạn muốn có giao diện std :: stream cho nó.


4
Xem xét rằng đây là giải pháp di động duy nhất, tôi không hiểu tại sao đây không phải là câu trả lời được chấp nhận hoặc được xếp hạng cao nhất.
Maarten

8

Có nhiều khả năng trình biên dịch của bạn cung cấp phương thức khởi tạo fstream dựa trên FILE, mặc dù nó không phải là tiêu chuẩn. Ví dụ:

FILE* f = fdopen(my_fd, "a");
std::fstream fstr(f);
fstr << "Greetings\n";

Nhưng theo tôi biết, không có cách nào di động để làm điều này.


2
Lưu ý rằng g ++ (chính xác) sẽ không cho phép điều này trong c ++ 11 chế độ
Đánh K Cowan

8

Một phần của động cơ ban đầu (không đánh dấu) của câu hỏi này là có khả năng truyền dữ liệu giữa các chương trình hoặc giữa hai phần của chương trình thử nghiệm bằng cách sử dụng tệp tạm thời được tạo an toàn, nhưng tmpnam () đưa ra một cảnh báo trong gcc, vì vậy tôi muốn để sử dụng mkstemp () thay thế. Đây là một chương trình thử nghiệm mà tôi đã viết dựa trên câu trả lời do Éric Malenfant đưa ra nhưng sử dụng mkstemp () thay vì fdopen (); điều này hoạt động trên hệ thống Ubuntu của tôi với các thư viện Boost được cài đặt:

#include <stdlib.h>
#include <string.h>
#include <assert.h>
#include <string>
#include <iostream>
#include <boost/filesystem.hpp>
#include <boost/iostreams/device/file_descriptor.hpp>
#include <boost/iostreams/stream.hpp>

using boost::iostreams::stream;
using boost::iostreams::file_descriptor_sink;
using boost::filesystem::path;
using boost::filesystem::exists;
using boost::filesystem::status;
using boost::filesystem::remove;

int main(int argc, const char *argv[]) {
  char tmpTemplate[13];
  strncpy(tmpTemplate, "/tmp/XXXXXX", 13);
  stream<file_descriptor_sink> tmp(mkstemp(tmpTemplate));
  assert(tmp.is_open());
  tmp << "Hello mkstemp!" << std::endl;
  tmp.close();
  path tmpPath(tmpTemplate);
  if (exists(status(tmpPath))) {
    std::cout << "Output is in " << tmpPath.file_string() << std::endl;
    std::string cmd("cat ");
    cmd += tmpPath.file_string();
    system(cmd.c_str());
    std::cout << "Removing " << tmpPath.file_string() << std::endl;
    remove(tmpPath);
  }
}


4

Tôi đã thử giải pháp được đề xuất ở trên cho libstdc ++ của Piotr Dobrogost và nhận thấy rằng nó có một lỗ hổng đau đớn: Do thiếu một hàm tạo chuyển động thích hợp cho istream, rất khó để lấy đối tượng istream mới được xây dựng ra khỏi hàm tạo . Một vấn đề khác với nó là nó làm rò rỉ một đối tượng FILE (thậm chí được cho là không phải là bộ mô tả tệp posix bên dưới). Dưới đây là một giải pháp thay thế để tránh những vấn đề này:

#include <fstream>
#include <string>
#include <ext/stdio_filebuf.h>
#include <type_traits>

bool OpenFileForSequentialInput(ifstream& ifs, const string& fname)
{
    ifs.open(fname.c_str(), ios::in);
    if (! ifs.is_open()) {
        return false;
    }

    using FilebufType = __gnu_cxx::stdio_filebuf<std::ifstream::char_type>;
    static_assert(  std::is_base_of<ifstream::__filebuf_type, FilebufType>::value &&
                    (sizeof(FilebufType) == sizeof(ifstream::__filebuf_type)),
            "The filebuf type appears to have extra data members, the cast might be unsafe");

    const int fd = static_cast<FilebufType*>(ifs.rdbuf())->fd();
    assert(fd >= 0);
    if (0 != posix_fadvise(fd, 0, 0, POSIX_FADV_SEQUENTIAL)) {
        ifs.close();
        return false;
    }

    return true;
}

Lệnh gọi posix_fadvise () cho thấy một khả năng sử dụng. Cũng lưu ý rằng ví dụ này sử dụng static_assertsử dụng C ++ 11, ngoài ra nó sẽ xây dựng tốt ở chế độ C ++ 03.


Ý bạn là gì về phiên bản phù hợp của phương thức khởi tạo di chuyển ? Bạn đã sử dụng phiên bản gcc nào? Có thể phiên bản này chưa triển khai các hàm tạo di chuyển - hãy xem Phương thức tạo di chuyển của ifsteam có bị xóa ngầm không? ?
Piotr Dobrogost

1
Đây là một hack phụ thuộc vào các chi tiết triển khai cơ bản. Tôi hy vọng không ai sử dụng điều này trong mã sản xuất.
davmac

-4

Sự hiểu biết của tôi là không có liên kết với con trỏ FILE hoặc bộ mô tả tệp trong mô hình đối tượng C ++ iostream để giữ cho mã di động.

Điều đó nói rằng, tôi đã thấy một số nơi đề cập đến mds-utils hoặc boost để giúp thu hẹp khoảng cách đó.


9
FILE * là tiêu chuẩn C và do đó C ++ vì vậy tôi không xem việc cho phép C ++ suối để làm việc với C suối có thể gây tổn hại cho tính di động
Piotr Dobrogost
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.