自定义 Stringstream - 转换 std::wstring & std::string

Custom Stringstream - Convert std::wstring & std::string

我有一个模板 class 来自 std::basic_stringstream<typename TString::value_type...>,如您所见。尝试转换它们时会出现问题。这可能是一个明显的问题,尽管我似乎无法找出解决方案。

作为 main 中的示例,我有一个简单的 std::wstring 并用 L"123" 初始化它。
std::wstring构造完成后,调用自定义basic_stringstream的运算符class(取决于std::wstringstd::string)。

出于调试目的检查 WCStringStream 对象,表明它包含输入字符串的第一个元素的地址,而不是字符串 L"123"。函数 to_bytesfrom_bytes 执行 return 正确的转换字符串,因此唯一剩下的问题是在两个运算符函数中调用的运算符:

*this << std::wstring_convert<...>().xx_bytes(s);

示例:
模板 class 是 std::wstring.
输入是 std::string.
&operator<<(const std::string &s) 正在通话中。
字符串已转换。
&operator<<(const std::wstring &s) 正在通话中。
字符串类型与模板类型匹配。
base-class (basic_stringstream) 的运算符被调用。 (或std::operator...

结果:
检查:{_Stringbuffer={_Seekhigh=0x007f6808 L"003BF76C췍췍췍췍췍췍췍췍췍...}...}
WCStringStream<std::wstring>::str() -> "003BF76C"

预期结果:
"123"

这里出了什么问题?


#define WIN32_LEAN_AND_MEAN
#define NOMINMAX
#include <Windows.h>
#include <iostream>
#include <sstream>
#include <codecvt>

template<class TString>
class WCStringStream : public std::basic_stringstream<typename TString::value_type,
    std::char_traits<typename TString::value_type>,
    std::allocator<typename TString::value_type> >
{
    typedef typename TString::value_type CharTraits;
    typedef std::basic_stringstream<CharTraits, std::char_traits<CharTraits>, std::allocator<CharTraits> > MyStream;
    //more typedefs...

public:
    //Constructor...
    inline WCStringStream(void) { }
    inline WCStringStream(const TString &s) : MyStream(s) { }
    //and more...
    //operator>> overloads...
    //defines for VS2010/2015 (C++11) included

    inline WCStringStream &operator<<(const std::wstring &s)
    {
        if (typeid(TString) == typeid(s))
            MyStream::operator<<(s.c_str());
        else
            *this << std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(s);
        return *this;
    }

    inline WCStringStream &operator<<(const std::string &s)
    {
        if (typeid(TString) == typeid(s))
            MyStream::operator<<(s.c_str());
        else
            *this << std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(s);
        return *this;
    }
};

//Example main
int main(int argc, char *argv[])
{
    typedef std::wstring fstring;

    WCStringStream<std::wstring> ws;
    WCStringStream<std::string> ss;

    ws << fstring(L"123");
    int a = 0;
    ws >> a;
    std::cout << a << std::endl;

    ss << fstring(L"123");
    int b = 0;
    ss >> b;
    std::cout << b << std::endl;

    return 0;
}

我目前正在 VS2015 中编译,但我也需要在 VS2010 上 运行。

首先:我认为在基础 class 中重载格式化函数的方法是 不明智的 而我 强烈地 建议不做!我确实意识到任何替代方案都需要更多的工作。

事实上,我认为你的主要问题实际上是你 没有 达到你的重载函数,这只是表明该方法是多么脆弱(我 认为 字符串描述了最终调用的重载,但我尚未验证这些确实准确,部分原因是问题中提供的代码缺少必要的上下文):

WCStringStream<std::string> stream;
stream << "calls std::operator<< (std::ostream&, char const*)\n";
stream << L"calls std::ostream::operator<< (void const*)\n";
stream << std::string("calls std::operator<< (std::ostream&, T&&)\n";
std::string const s("calls your operator\n");
stream << s;

由于无法更改字符串和字符串文字的重载输出运算符,并且它们在代码转换方面 错误 认为,我建议使用完全不同的方法,尽管它仍然不会没有危险(*):尽管使用比标准提供的代码更好的打包版本,但显式转换字符串。

假设总是使用 char 作为所有用途的字符类型,我会使用一个函数 wcvt() ,当将它们插入流中时,它会为所有字符串和字符串文字调用。由于在调用函数时它不知道将要使用的流的类型,因此它 return 本质上是对字符序列的引用,然后将其适当地转换为字符类型用于流。那将是这样的:

template <typename cT>
class wconvert {
    cT const* begin_;
    cT const* end_;
public:
    wconvert(std::basic_string<cT> const& s)
        : begin_(s.data())
        , end_(s.data() + s.size()) {
    }
    wconvert(cT const* s)
    : begin_(s)
    , end_(s + std::char_traits<cT>::length(s)) {
    }
    cT const* begin() const { return this->begin_; }
    cT const* end() const { return this->end_; }
    std::streamsize size() const { return this->end_ - this->begin_; }
};

template <typename cT>
wconvert<cT> wcvt(cT const* s) {
    return wconvert<cT>(s);
}
template <typename cT>
wconvert<cT> wcvt(std::basic_string<cT> const& s) {
    return wconvert<cT>(s);
}

template <typename cT>
std::basic_ostream<cT>& operator<< (std::basic_ostream<cT>& out,
                                    wconvert<cT> const& cvt) {
    return out.write(cvt.begin(), cvt.size());
}

std::ostream& operator<< (std::ostream& out, wconvert<wchar_t> const& cvt) {
    auto tmp = std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().to_bytes(cvt.begin(), cvt.end());
    return out.write(tmp.data(), tmp.size());
}

std::wostream& operator<< (std::wostream& out, wconvert<char> const& cvt) {
    auto tmp = std::wstring_convert<std::codecvt_utf8<wchar_t>, wchar_t>().from_bytes(cvt.begin(), cvt.end());
    return out.write(tmp.data(), tmp.size());
}

当然,使用这种方法需要在 s 可能是需要转换的字符串时使用 wcvt(s)。这样做很容易忘记,好像原来objective是为了而不是要记得用这样的转换。但是,我看不到任何对现有流系统不那么脆弱的替代方案。完全放弃使用流并使用完全独立的格式化系统 I/O 可能 产生不那么脆弱的方法。

(*) 最容易正确的方法是在程序中坚持只使用一种字符类型,总是 使用这种字符类型。我确实认为引入第二个字符类型 wchar_t 实际上是一个错误,并且通过引入 char16_tchar32_t 来进一步使现有的混乱更加复杂化是一个更大的错误。如果只有一种字符类型,char,我们会好得多,尽管它实际上不代表字符,而是编码的字节。

问题是显式调用基 class 运算符,它采用 const void *_Val 重载并打印地址。

MyStream::operator<<(s.c_str());

问题解决方法:

if (typeid(TString) == typeid(s))
{
    MyStream &os = *this;
    os << s.c_str();
}

当然调用 *this << s.c_str() 会导致递归,但是使用基数 class,它会为正确的字符类型调用全局重载运算符 wchar_t / char.

另一个可行的解决方案是使用成员函数 write 而不是运算符。