วิธีสร้าง c ++ fstream จากตัวอธิบายไฟล์ POSIX


94

โดยพื้นฐานแล้วฉันกำลังมองหา fdopen () เวอร์ชัน C ++ ฉันได้ค้นคว้าข้อมูลเล็กน้อยเกี่ยวกับเรื่องนี้และเป็นหนึ่งในสิ่งที่ดูเหมือนว่ามันควรจะง่าย แต่กลับกลายเป็นว่าซับซ้อนมาก ฉันพลาดอะไรบางอย่างในความเชื่อนี้ (คือมันง่ายจริงๆ)? ถ้าไม่มีมีห้องสมุดดีๆสักแห่งที่จัดการเรื่องนี้ได้หรือไม่?

แก้ไข: ย้ายโซลูชันตัวอย่างของฉันไปยังคำตอบแยกต่างหาก


@Kazark - ย้ายไปยังคำตอบแยกกันแล้วขอบคุณ
BD ที่ Rivenhill

Windows และ Linux สามารถทำmmapกับไฟล์และเปิดเผยเนื้อหาเป็นไบต์อาร์เรย์
eigenfield

คำตอบ:


74

จากคำตอบของÉric Malenfant:

AFAIK ไม่มีวิธีทำใน C ++ มาตรฐาน ขึ้นอยู่กับแพลตฟอร์มของคุณการใช้งานไลบรารีมาตรฐานของคุณอาจเสนอ (เป็นส่วนขยายที่ไม่เป็นมาตรฐาน) ตัวสร้าง fstream ที่ใช้ตัวอธิบายไฟล์เป็นอินพุต (เป็นกรณีของ libstdc ++, IIRC) หรือ FILE *

จากการสังเกตข้างต้นและงานวิจัยของฉันด้านล่างมีรหัสการทำงานในสองรูปแบบ หนึ่งสำหรับ libstdc ++ และอีกอันสำหรับ Microsoft Visual C ++


libstdc ++

มี__gnu_cxx::stdio_filebufเทมเพลตคลาสที่ไม่ได้มาตรฐานซึ่งสืบทอดstd::basic_streambufและมีตัวสร้างต่อไปนี้

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

พร้อมคำอธิบายตัวสร้างนี้เชื่อมโยงบัฟเฟอร์สตรีมไฟล์กับตัวอธิบายไฟล์ POSIX ที่เปิดอยู่

เราสร้างมันโดยผ่านจุดจับ POSIX (บรรทัดที่ 1) จากนั้นเราส่งต่อไปยังตัวสร้างของ istream เป็น basic_streambuf (บรรทัดที่ 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 ++

เคยมีตัวสร้างifstream รุ่นที่ไม่ได้มาตรฐานที่ใช้ตัวบอกไฟล์ POSIX แต่ไม่มีทั้งจากเอกสารปัจจุบันและจากโค้ด มีตัวสร้าง ifstream รุ่นอื่นที่ไม่ได้มาตรฐานที่ใช้ FILE *

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

และไม่ได้รับการจัดทำเป็นเอกสาร (ฉันไม่พบเอกสารเก่าที่จะนำเสนอ) เราเรียกมันว่า (บรรทัดที่ 1) โดยพารามิเตอร์เป็นผลมาจากการเรียก_fdopenเพื่อรับ C stream FILE * จากที่จับไฟล์ 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
ตอนนี้คำตอบที่ยอมรับเนื่องจากความสมบูรณ์ คนอื่นอาจสนใจวิธีแก้ปัญหาของฉันโดยใช้การเพิ่มซึ่งถูกย้ายไปยังคำตอบแยกต่างหาก
BD ที่ Rivenhill

1
สำหรับ linux: หากคุณดู ios_init.cc ใน gcc (ซอร์สที่ฉันมีสำหรับเวอร์ชัน 4.1.1) std :: cout เริ่มต้นโดยการเริ่มต้น stdio_sync_filebuf <char> รอบ ๆ ตัวอธิบายไฟล์ของคุณจากนั้นเริ่มต้นบน ostream รอบ ๆ stdio_sync_filebuf ของคุณ ถ่าน>. ฉันไม่สามารถอ้างได้ว่าสิ่งนี้จะมีเสถียรภาพ
Sparky

@Sparky มองไปที่std::coutการนำไปใช้เป็นความคิดที่ดี ฉันสงสัยว่าอะไรคือความแตกต่างระหว่างstdio_filebufและstdio_sync_filebuf?
Piotr Dobrogost

POSIX fds ใน MSVC เป็นการจำลอง Windows API สำหรับการดำเนินการไฟล์แตกต่างจาก POSIX ในหลาย ๆ ด้าน ได้แก่ ชื่อฟังก์ชันและชนิดข้อมูลของพารามิเตอร์ที่แตกต่างกัน Windows ใช้ภายในที่เรียกว่า "handles" เพื่อระบุอ็อบเจ็กต์ Windows API ต่างๆและประเภท Windows API HANDLE ถูกกำหนดเป็นโมฆะ * ขั้นต่ำจะไม่พอดีกับ "int" (ซึ่งเป็น 32 บิต) บนแพลตฟอร์ม 64 บิต ดังนั้นสำหรับ Windows คุณอาจสนใจมองหาสตรีมที่อนุญาตให้ทำงานผ่านไฟล์ Windows API HANDLE
ivan.ukr

40

AFAIK ไม่มีวิธีทำใน C ++ มาตรฐาน ขึ้นอยู่กับแพลตฟอร์มของคุณการใช้งานไลบรารีมาตรฐานของคุณอาจเสนอ (เป็นส่วนขยายที่ไม่เป็นมาตรฐาน) ตัวสร้าง fstream ที่ใช้ตัวอธิบายไฟล์ (นี่คือกรณีของ libstdc ++, IIRC) หรือFILE*เป็นอินพุต

อีกทางเลือกหนึ่งคือการใช้อุปกรณ์boost :: iostreams :: file_descriptorซึ่งคุณสามารถห่อboost :: iostreams :: stream ได้หากคุณต้องการมีอินเทอร์เฟซ std :: stream


4
เมื่อพิจารณาว่านี่เป็นโซลูชันแบบพกพาเท่านั้นฉันไม่เข้าใจว่าเหตุใดจึงไม่ใช่คำตอบที่ยอมรับหรือได้รับคะแนนสูงสุด
Maarten

8

มีโอกาสที่ดีที่คอมไพเลอร์ของคุณจะนำเสนอตัวสร้าง fstream แบบ FILE แม้ว่ามันจะไม่ได้มาตรฐานก็ตาม ตัวอย่างเช่น:

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

แต่เท่าที่ฉันรู้ไม่มีวิธีพกพาในการทำเช่นนี้


2
โปรดทราบว่า g ++ (อย่างถูกต้อง) จะไม่อนุญาตในโหมด c ++ 11
Mark K Cowan

8

ส่วนหนึ่งของแรงจูงใจดั้งเดิม (ไม่ระบุสถานะ) ของคำถามนี้คือการมีความสามารถในการส่งผ่านข้อมูลระหว่างโปรแกรมหรือระหว่างสองส่วนของโปรแกรมทดสอบโดยใช้ไฟล์ชั่วคราวที่สร้างขึ้นอย่างปลอดภัย แต่ tmpnam () แสดงคำเตือนใน gcc ดังนั้นฉันจึงต้องการ เพื่อใช้ mkstemp () แทน นี่คือโปรแกรมทดสอบที่ฉันเขียนตามคำตอบที่ได้รับจากÉric Malenfant แต่ใช้ mkstemp () แทน fdopen (); สิ่งนี้ใช้ได้กับระบบ Ubuntu ของฉันที่ติดตั้งไลบรารี Boost:

#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

ฉันได้ลองวิธีแก้ปัญหาที่เสนอข้างต้นสำหรับ libstdc ++ โดย Piotr Dobrogost และพบว่ามันมีข้อบกพร่องที่เจ็บปวด: เนื่องจากไม่มีตัวสร้างการเคลื่อนไหวที่เหมาะสมสำหรับ istream จึงเป็นเรื่องยากมากที่จะนำวัตถุ istream ที่สร้างขึ้นใหม่ออกจากฟังก์ชันการสร้าง . ปัญหาอีกประการหนึ่งคือการรั่วไหลของวัตถุ FILE (แม้คิดว่าไม่ใช่ตัวอธิบายไฟล์ posix ที่อยู่เบื้องหลัง) นี่คือทางเลือกอื่นที่หลีกเลี่ยงปัญหาเหล่านี้:

#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;
}

การเรียกใช้ posix_fadvise () แสดงให้เห็นถึงการใช้งานที่เป็นไปได้ โปรดทราบว่าตัวอย่างใช้static_assertและใช้ซึ่งเป็น C ++ 11 นอกเหนือจากนั้นควรสร้างได้ดีในโหมด C ++ 03


คุณหมายถึงอะไรโดยตัวสร้างการย้ายรุ่นที่เหมาะสม ? คุณใช้ gcc รุ่นอะไร บางทีเวอร์ชันนี้ยังไม่ได้ใช้ตัวสร้างการเคลื่อนไหว - โปรดดูที่ตัวสร้างการย้ายของ ifsteam ถูกลบโดยปริยายหรือไม่ เหรอ?
Piotr Dobrogost

1
นี่คือการแฮ็กที่ขึ้นอยู่กับรายละเอียดการใช้งานพื้นฐาน ฉันหวังว่าจะไม่มีใครใช้สิ่งนี้ในรหัสการผลิต
davmac

-4

ความเข้าใจของฉันคือไม่มีการเชื่อมโยงกับตัวชี้ FILE หรือตัวอธิบายไฟล์ในโมเดลอ็อบเจ็กต์ C ++ iostream เพื่อให้โค้ดพกพาได้

ที่กล่าวว่าฉันเห็นหลายแห่งอ้างถึงmds-utilsหรือ boost เพื่อช่วยลดช่องว่างนั้น


9
FILE * เป็นมาตรฐาน C และเป็น C ++ ดังนั้นฉันจึงไม่เห็นว่าการเปิดใช้งานสตรีม C ++ เพื่อทำงานกับสตรีม C อาจเป็นอันตรายต่อการพกพาได้อย่างไร
Piotr Dobrogost
โดยการใช้ไซต์ของเรา หมายความว่าคุณได้อ่านและทำความเข้าใจนโยบายคุกกี้และนโยบายความเป็นส่วนตัวของเราแล้ว
Licensed under cc by-sa 3.0 with attribution required.