【问题标题】:Custom Stringstream - Convert std::wstring & std::string自定义字符串流 - 转换 std::wstring 和 std::string
【发布时间】:2015-09-19 11:10:34
【问题描述】:

如您所见,我有一个从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
正在调用&amp;operator&lt;&lt;(const std::string &amp;s)
字符串已转换。
正在调用&amp;operator&lt;&lt;(const std::wstring &amp;s)
字符串类型与模板类型匹配。
基类 (basic_stringstream) 的运算符被调用。 (或std::operator...

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

【问题讨论】:

  • 我可以发誓 VS2010/2015 的股票 stringstream 已经为你做了这个
  • 从您的描述中,我并不完全清楚实际问题是什么。我的第一个想法是你应该看看模板专业化 [cprogramming.com/tutorial/template_specialization.html]
  • @Mgetz 还有std::stringstreamstd::wstringstream,我想将它们与此处未显示的其他功能“结合”起来。
  • typeid(..) == typeid(..) 应该是编译时检查吗?因为它目前不是....std::is_same&lt;TString, std::string&gt;::value 不是更合适,还是模板专业化?
  • 可以不使用模板特化,而是将转换接口设为通用,即std::string convert(std::wstring const&amp;); std::wstring convert(std::string const&amp;);,然后定义两个运算符,例如通过类型函数template&lt;class T&gt; using other_string_type = typename std::conditional&lt;std::is_same&lt;T, std::string&gt;::value, std::wstring, std::string&gt;::type; 然后WCStringStream&amp; operator&lt;&lt; (TString const&amp;); WCStringStream&amp; operator&lt;&lt; (other_string_type&lt;TString&gt; const&amp;);

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


【解决方案1】:

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

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

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,尽管它实际上不代表字符,而是编码的字节。

【讨论】:

  • 首先,感谢您的回答。这不是任何基类,它是一个无需进一步继承的类。我已经将问题更新得更清楚了,我认为*this &lt;&lt; 被调用的运算符很明显这些函数在类内部,但似乎不是。 - 是的,最简单的方法是在程序中使用一个字符,尽管有一些外部库和函数不支持 Unicode(目前)。事实上,最初的目标是,不检查(在这个类之外)它是 std::wstring 还是 std::string
【解决方案2】:

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

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

问题的解决方法:

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

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

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

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2011-04-06
    • 1970-01-01
    • 2018-11-23
    • 2018-03-21
    • 2011-11-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多