Custom Stringstream - Convert std::wstring & std::string
Custom Stringstream - Convert std::wstring & std::string
如您所见,我已经得到了一个来自std::basic_stringstream<typename TString::value_type...>
的模板类。问题发生在试图转换它们的时候。这可能是一个明显的问题,尽管我似乎找不到解决办法。
以main
为例,我有一个简单的std::wstring
,并用L"123"
初始化它。
在构造了std::wstring
之后,调用自定义basic_stringstream
类的操作符(取决于std::wstring
或std::string
)。
为调试目的检查WCStringStream
对象,显示它包含-而不是字符串L"123"
,而是输入字符串的第一个元素的地址。函数to_bytes
和from_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()
-> "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上运行。
首先:我认为在基类中重载格式化函数的方法是不明智的,我强烈建议不要这样做!我知道任何替代方案都需要更多的工作。
事实上,我认为你的主要问题实际上是你做没有到达你的重载函数无论如何只是显示出该方法是多么脆弱(我认为字符串描述了什么重载最终被调用,但我还没有验证这些确实是准确的,部分原因是问题中提供的代码缺乏必要的上下文):
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 operatorn");
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());
}
当然,使用这种方法需要在s
可能是需要转换的字符串时使用wcvt(s)
。这样做很容易忘记,似乎最初的目标是而不是必须记住这种转换的使用。然而,我没有看到任何替代方案比现有流系统更脆弱。完全放弃使用流并使用完全独立的格式化I/O系统可能产生更不脆弱的方法。
(*)最容易正确的方法是在程序中只使用一种字符类型,并且总是使用这种字符类型。我确实认为引入第二种字符类型wchar_t
实际上是一个错误,并且通过引入char16_t
和char32_t
来进一步复杂化现有的混乱是一个更大的错误。如果只有一种字符类型char
就好了,尽管它实际上并不表示字符,而是表示编码的字节。
问题在于显式调用基类操作符,该操作符接受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
代替操作符。
- cppcheck在const std::string[]上引发警告
- 将std::string传递给WriteConsole API
- 为std::string的某个索引赋值
- 使用 std::string () const 函数启动线程或未来
- 当我们进行一些操作时,应该使用什么'std::string'或'std::stringstream'?
- 如何更改大小(std::string)
- std::string 的对象真的可以移动吗?
- SegFault 同时使用 std::string::operator+= 和函数作为参数
- 无法从 std::string 中提取C++ Unicode 符号
- std::string 构造函数如何处理固定大小的 char[]?
- 真的没有来自 std::string_view 的 std::string 的显式构造函数吗?
- 将C++ std::string 转换为 UTF-16-LE 编码的字符串
- 重载 + 自己的类和 std::string 的运算符
- 如何使用 std::string 作为 QHash 的键?
- 将日语 wstring 转换为 std::string
- 可以从std::string继承以提供类型一致性吗
- 构造函数采用std::string_view与std::string并移动
- 在共享缓冲区内存中创建 ::std::string 对象
- std::string.size() 未知行为
- Valgrind 在 std::string::swap 中报告 SIGILL