【问题标题】:Opening stream via function通过函数打开流
【发布时间】:2011-09-25 08:56:35
【问题描述】:

我需要有关[io](f)streams 不可复制性质的帮助。

我需要在fstreams 周围提供一个hackish 包装器,以便在Windows 上处理文件名中包含Unicode 字符的文件。为此,我设计了一个包装函数:

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

std::swap 当然是罪魁祸首。我也尝试从函数返回流,但它会导致同样的问题。 std::istream 的复制构造函数是 deleted。我也尝试了std::move,但这并没有帮助。我该如何解决这个问题?

编辑:感谢@tibur 的想法,我终于找到了Keep It Simple (TM) 的好方法,而且功能强大。从某种意义上说它仍然是 hackish,因为它依赖于所使用的 Windows 标准 C++ 库,但由于只有两个 真正的 库在使用,所以这对我来说不是问题。

#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(buffer) );
}
# 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

在用户代码中:

auto stream_ptr( open_ifstream(filename) );
auto &stream = *stream_ptr;
if( !stream )
    return emit_error( "Unable to open nectar file: " + filename );

这取决于 C++0x &lt;memory&gt;auto 关键字。当然,您不能只使用close 生成的stream 变量,而是GNU Libstdc++ std::istream 析构函数会负责关闭文件,因此在任何地方都不需要额外的内存管理。

【问题讨论】:

  • 你为什么要通过 iostreams 推送一个 UTF-16 字符串?首先,我不认为 _wfopen 需要一个 UTF-16 字符串。我相当确定,在基于 GCC 的编译器上,wchar_t 字符串应该是 UTF-32。由于 wchar_t 的长度为 32 位,这与 Visual Studio 下的长度为 16 位不同。其次,您确定不能只向他们传递 UTF-8 字符串吗?诚然,我不知道 GCC 的标准 C++ 库是如何在 Windows 上实现的,但在 UNIX 上,它们采用 UTF-8 字符串。所以我希望他们在幕后为您进行转换。
  • 为什么不将文件名与程序逻辑的其余部分分离,并为使用 GetShortPathName 的 Windows 提供一个包装器——这样您就可以将所有文件名统一视为 char*
  • @Nicol:UTF-16 是 Win32 API 的工作方式,Windows 上的 GCC 遵循这一点,与其他原生 Windows 东西兼容。
  • 你不需要fclose()吗?根据文档“当 stdio_filebuf 关闭/销毁时,FILE* 不会自动关闭。”

标签: c++ c++11 copy-constructor istream deleted-functions


【解决方案1】:

怎么样:

ifstream * open_ifstream(const string &filename);

【讨论】:

  • 呃.. 如果有的话,返回 unique_ptr&lt;ifstream&gt;,而不是 ifstream*
【解决方案2】:

不能直接用rdbuf的成员函数来设置stream的缓冲区吗?

【讨论】:

  • 你仍然需要动态分配streambuf,以便它在函数返回后仍然存在,你必须想办法确保它被删除当流结束时。
  • 是的,但我没有得到零件有问题的印象。我的意思是,如果他的std::swap 线路有效,他无论如何都必须这样做。
  • 互联网上的各种消息来源告诉我,当您在流上调用 close() 时,libstdc++ 内部将处理 __gnu_cxx::stdio_filebuf 对象。但是你的建议的问题是我需要分配给rdbuf返回的指针,或者至少“重建”指向的对象,因为返回的filebuf_type没有合适的open方法:(我无论如何你都需要“热交换”缓冲区对象......
  • "Hotswap the buffer" 正是 rdbuf 所做的:stream.rdbuf(buffer) 将丢弃(并返回)旧的流缓冲区并用您传入的任何内容替换它。我见过人们使用它例如,使cout 打印到文件中。
【解决方案3】:

这是一个适度无干扰的想法:

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

用法:

std::string filename = getFilename();
windowify(filename);
std::ifstream infile(filename.c_str());

【讨论】:

  • 一个警告 IMO:GetShortPathName 需要一个绝对路径,这将需要另外两次调用 GetFullPathName 和一些 C 字符串连接混乱。我宁愿修复我的 __gnu_cxx 东西。
  • @Rubenvb:来自documentation:“lpszLongPath 参数指定的路径不必是完整路径或长路径。”我想我之前已经成功地在裸文件名和相对路径上使用了它......请记住,NTFS 文件名不必是合法的 UTF16,而只是以零结尾的 16 位字符串,所以你可能想要允许一点更通用。
【解决方案4】:

我建议做一个小的改进:使用_wopen(或_wsopen_s)而不是_wfopen。您将获得一个文件描述符 (int),您可以将其传递给 stdio_filebuf 来代替 FILE*。通过这种方式,您应该避免泄漏任何资源(正如 marcin 所指出的)

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2019-09-02
    • 2021-04-15
    • 1970-01-01
    • 1970-01-01
    • 2017-03-07
    • 1970-01-01
    相关资源
    最近更新 更多