I need help with the non-copyable nature of [io](f)stream
s.
I need to provide a hackish wrapper around fstream
s in order to handle files with unicode characters in their filenames on Windows. For this, I devised a wrapper function:
bool open_ifstream( istream &stream, const string &filename )
{
#ifdef __GLIBCXX__
FILE* result = _wfopen( convert_to_utf16(filename).c_str(), L"r" );
if( result == 0 )
return false;
__gnu_cxx::stdio_filebuf<char>* buffer = new __gnu_cxx::stdio_filebuf<char>( result, std::ios_base::in, 1 );
istream stream2(buffer);
std::swap(stream, stream2);
#elif defined(_MSC_VER)
stream.open( convert_to_utf16(filename) );
#endif
return !!stream;
}
With of course the std::swap
line being the culprit. I also tried returning the stream from the function, but it leads to the same problem. The copy constructor of a std::istream
is delete
d. I also tried a std::move
but that didn't help. How do I work around this problem?
EDIT: I finally found a good way to Keep It Simple (TM)
and yet functional, thanks to @tibur's idea. It's still hackish in the sense that it depends on the Windows Standard C++ library used, but as there's only two real ones in use, it's not really a problem for me.
#include <fstream>
#include <memory>
#if _WIN32
# if __GLIBCXX__
# include<ext/stdio_filebuf.h>
unique_ptr<istream> open_ifstream( const string &filename )
{
FILE* c_file = _wfopen( convert_to_utf16(filename).c_str(), L"r" );
__gnu_cxx::stdio_filebuf<char>* buffer = new __gnu_cxx::stdio_filebuf<char>( c_file, std::ios_base::in, 1 );
return std::unique_ptr<istream>( new istream(buff开发者_Go百科er) );
}
# elif _MSC_VER
unique_ptr<ifstream> open_ifstream( const string &filename )
{
return unique_ptr<ifstream>(new ifstream( convert_to_utf16(filename)) );
}
# else
# error unknown fstream implementation
# endif
#else
unique_ptr<ifstream> open_ifstream( const string &filename )
{
return unique_ptr<ifstream>(new ifstream(filename) );
}
#endif
And in user code:
auto stream_ptr( open_ifstream(filename) );
auto &stream = *stream_ptr;
if( !stream )
return emit_error( "Unable to open nectar file: " + filename );
Which depends on C++0x <memory>
and the auto
keyword. Of course you can't just close
the resulting stream
variable, but the GNU Libstdc++ std::istream
destructor does take care of closing the file, so no extra memory management is required anywhere.
What about:
ifstream * open_ifstream(const string &filename);
Couldn't you just use the rdbuf
member function to set stream
's buffer directly?
Here's a moderately unintrusive idea:
#include <iconv.h>
#include <algorithm>
void windowify(std::string & filename)
{
#ifdef WIN32
assert(filename.length() < 1000);
wchar_t wbuf[1000];
char cbuf[1000];
char * ip = &cbuf[0];
char * op = reinterpret_cast<char*>(&wbuf[0]);
size_t ib = filename.length(), ob = 1000;
std::fill(cbuf + filename.length(), cbuf + 1000, 0);
std::copy(filename.begin(), filename.end(), cbuf);
iconv_t cd = iconv_open("WCHAR_T", "UTF-8");
iconv(cd, &ip, &ib, &op, &ob);
iconv_close(cd);
wchar_t sfnbuf[1000];
std::fill(cbuf, cbuf + 1000, 0);
ib = GetShortPathNameW(wbuf, sfnbuf, 1000);
ob = 1000;
ip = reinterpret_cast<char*>(&wbuf[0]);
op = &cbuf[0];
cd = iconv_open("UTF-8", "WCHAR_T");
iconv(cd, &ip, &ib, &op, &ob);
iconv_close(cd);
filename = std::string(cbuf);
#endif
}
Usage:
std::string filename = getFilename();
windowify(filename);
std::ifstream infile(filename.c_str());
I would suggest a small improvement: use _wopen
(or _wsopen_s
) instead of _wfopen
. You will get a file descriptor (int
) that you can pass to the stdio_filebuf
in place of the the FILE*
. In this way you should avoid leaking any resource (as pointed out by marcin)
精彩评论