自定义字符串流 - 转换std :: wstring&的std :: string

时间:2015-09-19 11:10:34

标签: c++ templates c++11 visual-c++ c++03

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

作为main中的示例,我有一个简单的std::wstring并使用L"123"对其进行初始化。
构建std::wstring后,系统会调用自定义basic_stringstream类的运算符(取决于std::wstringstd::string)。

检查WCStringStream对象以进行调试,表明它包含 - 而不是字符串L"123",即输入字符串的第一个元素的地址。函数to_bytesfrom_bytes确实返回正确的转换字符串,因此唯一的问题是在两个运算符函数中调用运算符:

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

实施例
模板类是std::wstring 输入为std::string 正在调用&operator<<(const std::string &s) 字符串被转换 正在调用&operator<<(const std::wstring &s) 字符串类型与模板类型匹配 调用基类(basic_stringstream)的运算符。 (或std::operator...

结果:
检查:{_Stringbuffer={_Seekhigh=0x007f6808 L"003BF76C췍췍췍췍췍췍췍췍췍...}...}
WCStringStream<std::wstring>::str() - &gt; "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上运行。

2 个答案:

答案 0 :(得分:2)

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

事实上,我认为你的主要问题实际上是你到达你的重载函数无论如何只是显示这种方法有多么脆弱(我认为字符串描述了什么重载最终被调用,但我没有证实这些确实是准确的,部分是因为问题中提供的代码缺乏必要的上下文):

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(),在将它们插入流时,将调用所有字符串和字符串文字。由于在调用函数时它不知道它将要使用的流的类型,它将基本上返回对字符序列的引用,然后对于用于流的字符类型进行适当转换。这将是以下几点:

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

当然,只要wcvt(s)可能是需要转换的字符串,使用此方法就需要使用s。很容易忘记这样做,似乎最初的目标是必须记住这种转换的使用。但是,我没有看到任何使用现有流系统不那么脆弱的替代方案。完全放弃使用流并使用完全独立的格式化I / O 系统可能产生不那么脆弱的方法。

(*)最简单的方法是坚持使用该字符类型的程序中的字符类型和始终。我确实认为引入第二个字符类型wchar_t实际上是一个错误,并且通过引入char16_tchar32_t来进一步使现有混乱变得复杂,这是一个更大的错误。只有一种字符类型char,我们会好多了,虽然它实际上不代表字符而是代码的字节。

答案 1 :(得分:1)

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

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

解决问题的方法:

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

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

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