Bir POSIX dosya tanımlayıcısından bir c ++ fstream nasıl oluşturulur?


94

Temel olarak fdopen () 'in C ++ sürümünü arıyorum. Bunun üzerine biraz araştırma yaptım ve kolay olması gereken şeylerden biri ama çok karmaşık olduğu ortaya çıktı. Bu inançta bir şeyi özlüyor muyum (yani gerçekten çok kolay)? Değilse, bunun üstesinden gelebilecek iyi bir kütüphane var mı?

DÜZENLEME: Örnek çözümümü ayrı bir cevaba taşıdı.


@Kazark - şimdi ayrı bir cevaba taşındı, teşekkürler.
Rivenhill'de BD

Windows ve Linux mmapdosyaya yapabilir ve içeriğini bayt dizisi olarak ortaya çıkarabilir .
eigenfield

Yanıtlar:


74

Éric Malenfant'ın verdiği cevaptan:

AFAIK, bunu standart C ++ ile yapmanın bir yolu yok. Platformunuza bağlı olarak, standart kitaplığı uygulamanız (standart olmayan bir uzantı olarak) bir dosya tanımlayıcısını girdi olarak alan bir fstream kurucusu sunabilir. (Libstdc ++, IIRC için durum böyledir) veya bir DOSYA *.

Yukarıdaki gözlemlere ve aşağıda yaptığım araştırmaya dayanarak, çalışma kodunun iki çeşidi vardır; biri libstdc ++ için ve diğeri Microsoft Visual C ++ için.


libstdc ++

Aşağıdaki __gnu_cxx::stdio_filebufyapıcıyı devralan std::basic_streambufve sahip olan standart olmayan bir sınıf şablonu var

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

with description Bu yapıcı, bir dosya akışı arabelleğini açık bir POSIX dosya tanımlayıcısıyla ilişkilendirir.

Bunu POSIX handleyini (1. satır) geçerek oluşturuyoruz ve sonra istream'in yapıcısına basic_streambuf (2. satır) olarak iletiyoruz:

#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 ++

Önceden POSIX dosya tanımlayıcısını alan ifstream yapıcısının standart olmayan bir sürümü vardı , ancak hem mevcut belgelerde hem de kodda eksik . İfstream'in kurucusunun FILE * alan standart olmayan başka bir sürümü daha var

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

ve belgelenmemiş (mevcut olacağı herhangi bir eski belgeyi bile bulamadım). POSIX dosya tanıtıcısından C akış FILE * elde etmek için _fdopen çağrısının sonucu olan parametresi ile (1. satır) diyoruz .

#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
Now the accepted answer due to completeness. Others may be interested in my solution using boost, which has been moved to a separate answer.
BD at Rivenhill

1
For linux: If you look at ios_init.cc in gcc (the source I have is for version 4.1.1) std::cout is initialised by initialising a stdio_sync_filebuf<char> around your file descriptor, then initialising on ostream around your stdio_sync_filebuf<char>. I can't claim that this is going to be stable though.
Sparky

@Sparky Looking into std::cout implementation is a good idea. I'm wondering what's the difference between stdio_filebuf and stdio_sync_filebuf?
Piotr Dobrogost

POSIX fds in MSVC are emulation. Windows API for file operations differs from POSIX ones in many ways - different functions names and data types of parameters.Windows internally uses so called "handles" to identify various Windows API objects, and Windows API type HANDLE is defined as void*, so at minimum it will not fit into "int" (which is 32-bit) on 64-bit platforms. So for Windows you may be interested in looking for stream that allows to work over Windows API file HANDLE.
ivan.ukr

40

AFAIK, there is no way to do this in standard C++. Depending on your platform, your implementation of the standard library may offer (as a nonstandard extension) a fstream constructor taking a file descriptor (This is the case for libstdc++, IIRC) or a FILE* as an input.

Another alternative would be to use a boost::iostreams::file_descriptor device, which you could wrap in a boost::iostreams::stream if you want to have an std::stream interface to it.


4
Considering that this is the only portable solution, I don't understand why this isn't the accepted or top-rated answer.
Maarten

8

There's a good chance your compiler offers a FILE-based fstream constructor, even though it's non-standard. For example:

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

But as far as I know, there's no portable way to do this.


2
Note that g++ (correctly) won't allow this in c++11 mode
Mark K Cowan

8

Part of the original (unstated) motivation of this question is to have the ability to pass data either between programs or between two parts of a test program using a safely created temporary file, but tmpnam() throws a warning in gcc, so I wanted to use mkstemp() instead. Here is a test program that I wrote based on the answer given by Éric Malenfant but using mkstemp() instead of fdopen(); this works on my Ubuntu system with Boost libraries installed:

#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

I've tried the solution proposed above for libstdc++ by Piotr Dobrogost, and found that it had a painful flaw: Due to the lack of a proper move constructor for istream, it's very difficult to get the newly constructed istream object out of the creating function. Another issue with it is that it leaks a FILE object (even thought not the underlying posix file descriptor). Here's an alternative solution that avoids these issues:

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

The call to posix_fadvise() demonstrates a potential use. Also note that the example uses static_assert and using which are C++ 11, other than that it should build just fine in C++ 03 mode.


What do you mean by proper version of move constructor? What version of gcc did you use? Maybe this version did not have move constructors implemented yet – see Is the move constructor of ifsteam implicitly deleted??
Piotr Dobrogost

1
This is a hack that is dependent on underlying implementation details. I would hope nobody ever uses this in production code.
davmac

-4

My understanding is that there is no association with FILE pointers or file descriptors in the C++ iostream object model in order to keep code portable.

That said, I saw several places refer to the mds-utils or boost to help bridge that gap.


9
FILE* is standard C and thus C++ so I don't see how enabling C++ streams to work with C streams could harm portability
Piotr Dobrogost
Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.