简体   繁体   English

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

[英]Custom Stringstream - Convert std::wstring & std::string

I've got a template class derived from std::basic_stringstream<typename TString::value_type...> , as you can see. 我有一个派生自std::basic_stringstream<typename TString::value_type...>的模板类,如您所见。 The problem happens while trying to convert them. 尝试转换它们时会出现问题。 It's probably an obvious problem, though I cannot seem to figure out the solution. 这可能是一个明显的问题,但我似乎无法找出解决方案。

As example in main , I have a simple std::wstring and initialize it with L"123" . 作为main示例,我有一个简单的std::wstring并用L"123"初始化它。
After the std::wstring has been constructed, the operator of the custom basic_stringstream class is called (depending on std::wstring or std::string ). 在构造std::wstring之后,调用自定义basic_stringstream类的运算符(取决于std::wstringstd::string )。

Inspecting the WCStringStream object for debugging purposes, shows that it contains - instead of the string L"123" , the address of the first element of the entered string. 检查WCStringStream对象以进行调试,表明它包含 - 而不是字符串L"123" ,即输入字符串的第一个元素的地址。 The functions to_bytes and from_bytes do return the correct converted string, so the only problem left is the operator being called in both operator-functions: 函数to_bytesfrom_bytes确实返回正确的转换字符串,因此唯一的问题是在两个运算符函数中调用运算符:

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

Example: 例:
Template class is std::wstring . 模板类是std::wstring
Input is a std::string . 输入是一个std::string
&operator<<(const std::string &s) is being called. 正在调用&operator<<(const std::string &s)
String is converted. 字符串已转换。
&operator<<(const std::wstring &s) is being called. 正在调用&operator<<(const std::wstring &s)
String-type matches with template type. 字符串类型与模板类型匹配。
Operator of base-class ( basic_stringstream ) is called. 调用基类( basic_stringstream )的运算符。 (Or std::operator... ) (或者std::operator...

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

Expected result: 预期结果:
"123"

What's going wrong here ? 这里出了什么问题?


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

I'm compiling currently in VS2015 but I'd need it to run on VS2010 too. 我目前正在编译VS2015,但我也需要它在VS2010上运行。

First off: I think the approach to overload formatting function in a base class is ill-advised and I strongly recommend to not do it ! 首先:我认为在基类中重载格式化函数的方法是不明智的 ,我强烈建议不要这样做 I do realize that any alternative will require a bit more work. 我确实意识到任何替代方案都需要更多的工作。

In fact, I think your primary problem is actually that you do not reach your overloaded functions anyway just showing how fragile the approach is (I think the string describe what overload ends up being called but I haven't verified that these are indeed accurate, partly because the code provided in the question is lacking necessary context): 事实上,我认为你的主要问题实际上是你没有达到你的重载函数,只是表明方法是多么脆弱(我认为字符串描述了什么超载最终被调用,但我还没有证实这些确实是准确的,部分原因是问题中提供的代码缺乏必要的背景):

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;

Since the overloaded output operators for strings and string literals can't be changed and they do the wrong think with respect to code conversions, I recommend using an entirely different approach although it still won't be without peril(*): convert the strings explicitly although using a more nicely packaged version of the code than the standard provides. 由于字符串和字符串文字的重载输出操作符无法更改,并且他们在代码转换方面做错了,我建议使用完全不同的方法,尽管它仍然不会没有危险(*):转换字符串尽管使用了比标准提供的更好的打包版本的代码。

Assuming always using char as character type for all uses I would use a function wcvt() which is called for all strings and string-literals when inserting them into a stream. 假设始终将char用作所有用途的字符类型,我将使用函数wcvt() ,在将它们插入流时,将为所有字符串和字符串文字调用。 Since at the point the function is being called it wouldn't know the type of the stream it is going to be used with, it would return essentially a reference to the character sequence which is then converted appropriately for the character type used for the stream. 由于在调用函数时它不知道它将要使用的流的类型,它将基本上返回对字符序列的引用,然后对于用于流的字符类型进行适当转换。 That would be something along these lines: 这将是以下几点:

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

Of course, using this approach requires the use of wcvt(s) whenever s may be a string which needs to be converted. 当然,使用这种方法需要使用wcvt(s)只要s可能是需要转换的字符串。 It is easy to forget doing so and it seems the original objective was to not have to remember the use of such a conversion. 很容易忘记这样做,似乎最初的目标是不必记住这种转换的使用。 However, I don't see any alternative which is less fragile with the system of existing streams. 但是,我没有看到任何使用现有流系统不那么脆弱的替代方案。 Entirely abandoning the use of streams and using an entirely separate system of formatted I/O may yield less fragile approach. 完全放弃使用流并使用完全独立的格式化I / O系统可能会产生不太脆弱的方法。

(*) The approach easiest to get right is to stick with just on character type in a program and always using this character type. (*)最容易实现的方法是坚持使用程序中的字符类型并始终使用此字符类型。 I do believe it was actually an error to introduce a second character type, wchar_t , and it an even bigger error to further complicate the existing mess by having also introduced char16_t and char32_t . 我确实认为引入第二个字符类型wchar_t实际上是一个错误,并且通过引入char16_tchar32_t来进一步使现有的混乱变得更复杂,这是一个更大的错误。 We'd be much better off there were just one character type, char , although it actually wouldn't represent character but bytes of an encoding. 只有一种字符类型char ,我们会好得多,虽然它实际上不代表字符而是代码字节。

The problem was to explicitly call the base class operator, which takes the const void *_Val overload and prints the address. 问题是显式调用基类运算符,它接受const void *_Val重载并打印地址。

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

The solution to the problem: 解决问题的方法:

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

Of course calling *this << s.c_str() results in recursion, but the using the base class, it calls the global overloaded operator for the correct char-type wchar_t / char . 当然调用*this << s.c_str()导致递归,但是使用基类,它会调用全局重载运算符来获取正确的char-type wchar_t / char

An also working solution is to use the member-function write instead of the operator. 另一个有效的解决方案是使用成员函数write而不是运算符。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM