Avoid encoding changes of open streams if possible

Changing the codecvt_facet of a file stream after the file has been opened
does not work with clang on OS X. Therefore we avoid it if possible (i. e. the
new encoding is the same as the old one).
This commit is contained in:
Georg Baum 2016-01-31 12:49:17 +01:00
parent 250b058192
commit 92d0835e14
2 changed files with 30 additions and 13 deletions

View File

@ -88,6 +88,7 @@ public:
} else } else
out_cd_ = (iconv_t)(-1); out_cd_ = (iconv_t)(-1);
} }
string const & encoding() const { return encoding_; }
protected: protected:
virtual ~iconv_codecvt_facet() virtual ~iconv_codecvt_facet()
{ {
@ -375,37 +376,51 @@ odocstream & operator<<(odocstream & os, SetEnc e)
if (has_facet<iconv_codecvt_facet>(os.rdbuf()->getloc())) { if (has_facet<iconv_codecvt_facet>(os.rdbuf()->getloc())) {
// This stream must be a file stream, since we never imbue // This stream must be a file stream, since we never imbue
// any other stream with a locale having a iconv_codecvt_facet. // any other stream with a locale having a iconv_codecvt_facet.
iconv_codecvt_facet const & facet =
use_facet<iconv_codecvt_facet>(os.rdbuf()->getloc());
// FIXME Changing the codecvt facet of an open file is allowed,
// but unsafe for facets that use internal state (see the thread
// "iostreams: Does imbue() need to be called before open()?"
// in comp.std.c++.
// Currently it seems to work with gcc and MSVC, but not with
// clang on OS X.
// Avoid imbueing with the same encoding again if possible.
if (facet.encoding() == e.encoding)
return os;
// Flush the stream so that all pending output is written // Flush the stream so that all pending output is written
// with the old encoding. // with the old encoding.
os.flush(); os.flush();
locale locale(os.rdbuf()->getloc(), locale locale(os.rdbuf()->getloc(),
new iconv_codecvt_facet(e.encoding, ios_base::out)); new iconv_codecvt_facet(e.encoding, ios_base::out));
// FIXME Does changing the codecvt facet of an open file
// stream always work? It does with gcc 4.1, but I have read
// somewhere that it does not with MSVC.
// What does the standard say?
os.imbue(locale); os.imbue(locale);
} }
return os; return os;
} }
//CHECKME: I just copied the code above, and have no idea whether it
//is correct... (JMarc)
idocstream & operator<<(idocstream & is, SetEnc e) idocstream & operator<<(idocstream & is, SetEnc e)
{ {
if (has_facet<iconv_codecvt_facet>(is.rdbuf()->getloc())) { if (has_facet<iconv_codecvt_facet>(is.rdbuf()->getloc())) {
// This stream must be a file stream, since we never imbue // This stream must be a file stream, since we never imbue
// any other stream with a locale having a iconv_codecvt_facet. // any other stream with a locale having a iconv_codecvt_facet.
// Flush the stream so that all pending output is written iconv_codecvt_facet const & facet =
// with the old encoding. use_facet<iconv_codecvt_facet>(is.rdbuf()->getloc());
//is.flush();
// FIXME Changing the codecvt facet of an open file is allowed,
// but unsafe for facets that use internal state (see the thread
// "iostreams: Does imbue() need to be called before open()?"
// in comp.std.c++.
// Currently it seems to work with gcc and MSVC, but not with
// clang on OS X.
// Avoid imbueing with the same encoding again if possible.
if (facet.encoding() == e.encoding)
return is;
locale locale(is.rdbuf()->getloc(), locale locale(is.rdbuf()->getloc(),
new iconv_codecvt_facet(e.encoding, ios_base::in)); new iconv_codecvt_facet(e.encoding, ios_base::in));
// FIXME Does changing the codecvt facet of an open file
// stream always work? It does with gcc 4.1, but I have read
// somewhere that it does not with MSVC.
// What does the standard say?
is.imbue(locale); is.imbue(locale);
} }
return is; return is;

View File

@ -42,6 +42,8 @@ typedef std::basic_ostream<char_type> odocstream;
/// File stream for reading UTF8-encoded files with automatic conversion to /// File stream for reading UTF8-encoded files with automatic conversion to
/// UCS4. /// UCS4.
/// Buffering must be switched off if the encoding is changed after
/// construction by calling rdbuf()->pubsetbuf(0, 0).
class ifdocstream : public std::basic_ifstream<char_type> { class ifdocstream : public std::basic_ifstream<char_type> {
typedef std::basic_ifstream<char_type> base; typedef std::basic_ifstream<char_type> base;
public: public: