std::字符串和多个串联
std::string and multiple concatenations
让我们考虑这个片段,并假设a、b、c和d是非空字符串。
std::string a, b, c, d;
d = a + b + c;
当计算这3个std::string
实例的总和时,标准库实现创建第一个临时std::string
对象,在其内部缓冲区中复制a
和b
的级联缓冲区,然后在临时字符串和c
之间执行相同的操作。
一位程序员同事强调,operator+(std::string, std::string)
可以被定义为返回std::string_helper
,而不是这种行为。
这个对象的作用是将实际的连接推迟到它被广播到std::string
中的那一刻。显然,operator+(std::string_helper, std::string)
将被定义为返回相同的助手,这将"记住"它有一个额外的串联要执行的事实。
这样的行为可以节省创建n-1个临时对象、分配缓冲区、复制它们等的CPU成本。所以我的问题是:为什么它还没有这样工作?我想不出任何缺点或限制。
为什么它还没有这样工作?
我只能推测为什么它最初是这样设计的。也许字符串库的设计者根本没有想到;也许他们认为在某些情况下,额外的类型转换(见下文)可能会使行为过于令人惊讶。它是最古老的C++库之一,许多我们认为理所当然的智慧在过去几十年里根本不存在。
至于为什么它没有被更改为这样工作:它可以通过添加额外的用户定义类型转换来破坏现有代码。隐式转换最多只能涉及一个用户定义的转换。这是由C++113.3.3.1.2/1:规定的
用户定义的转换序列包括一个初始标准转换序列,然后是一个用户定义转换,然后是第二个标准转换序列。
考虑以下内容:
struct thingy {
thingy(std::string);
};
void f(thingy);
f(some_string + another_string);
如果some_string + another_string
的类型为std::string
,则此代码可以。可以通过转换构造函数将其隐式转换为thingy
。然而,如果我们要更改operator+
的定义以给出另一种类型,那么它将需要两次转换(string_helper
到string
到thingy
),因此无法编译。
因此,如果字符串构建的速度很重要,则需要使用其他方法,如+=
的串联。或者,根据Matthieu的回答,不要担心,因为C++11以不同的方式解决了效率低下的问题。
显而易见的答案是:因为标准不允许这样做。在某些情况下,它通过引入额外的用户定义转换来影响代码:如果C
是一个用户定义构造函数采用std::string
的类型,那么它将使:
C obj = stringA + stringB;
非法。
这取决于情况。
在C++03中,确切地说,可能有一点效率低下(顺便说一句,与Java和C#相当,因为它们使用字符串内部处理)。使用可以缓解这种情况
d = std::string("") += a += b +=c;
这并不是真的。。。惯用的
在C++11中,operator+
被重载以用于右值引用。意思是:
d = a + b + c;
转换为:
d.assign(std::move(operator+(a, b).append(c)));
这(几乎)是你能得到的最有效的。
C++11版本中剩下的唯一低效之处是,内存在一开始并没有一次性保留,因此可能会进行重新分配和复制,最多2次(对于每个新字符串)。尽管如此,因为追加是O(1)摊销的,除非C比B长得多,否则最坏的情况下应该进行一次重新分配+复制。当然,我们在这里谈论的是POD复制(所以是memcpy
呼叫)。
听起来像这样的东西已经存在:std::stringstream
。
只有您有<<
而不是+
。仅仅因为std::string::operator +
的存在,并不能使其成为最有效的选择。
我认为如果你使用+=
,那么它会更快一点:
d += a;
d += b;
d += c;
它应该更快,因为它不会创建临时对象。或者简单地说,
d.append(a).append(b).append(c); //same as above: i.e using '+=' 3 times.
不进行单个+
串联的字符串,尤其是不在循环中进行串联的主要原因是它具有O(n2)复杂性。
对于O(n)复杂性,一个合理的替代方案是使用简单的字符串生成器,如
template< class Char >
class ConversionToString
{
public:
// Visual C++ 10.0 has some DLL linking problem with other types:
CPP_STATIC_ASSERT((
std::is_same< Char, char >::value || std::is_same< Char, wchar_t >::value
));
typedef std::basic_string< Char > String;
typedef std::basic_ostringstream< Char > OutStringStream;
// Just a default implementation, not particularly efficient.
template< class Type >
static String from( Type const& v )
{
OutStringStream stream;
stream << v;
return stream.str();
}
static String const& from( String const& s )
{
return s;
}
};
template< class Char, class RawChar = Char >
class StringBuilder;
template< class Char, class RawChar >
class StringBuilder
{
private:
typedef std::basic_string< Char > String;
typedef std::basic_string< RawChar > RawString;
RawString s_;
template< class Type >
static RawString fastStringFrom( Type const& v )
{
return ConversionToString< RawChar >::from( v );
}
static RawChar const* fastStringFrom( RawChar const* s )
{
assert( s != 0 );
return s;
}
static RawChar const* fastStringFrom( Char const* s )
{
assert( s != 0 );
CPP_STATIC_ASSERT( sizeof( RawChar ) == sizeof( Char ) );
return reinterpret_cast< RawChar const* >( s );
}
public:
enum ToString { toString };
enum ToPointer { toPointer };
String const& str() const { return reinterpret_cast< String const& >( s_ ); }
operator String const& () const { return str(); }
String const& operator<<( ToString ) { return str(); }
RawChar const* ptr() const { return s_.c_str(); }
operator RawChar const* () const { return ptr(); }
RawChar const* operator<<( ToPointer ) { return ptr(); }
template< class Type >
StringBuilder& operator<<( Type const& v )
{
s_ += fastStringFrom( v );
return *this;
}
};
template< class Char >
class StringBuilder< Char, Char >
{
private:
typedef std::basic_string< Char > String;
String s_;
template< class Type >
static String fastStringFrom( Type const& v )
{
return ConversionToString< Char >::from( v );
}
static Char const* fastStringFrom( Char const* s )
{
assert( s != 0 );
return s;
}
public:
enum ToString { toString };
enum ToPointer { toPointer };
String const& str() const { return s_; }
operator String const& () const { return str(); }
String const& operator<<( ToString ) { return str(); }
Char const* ptr() const { return s_.c_str(); }
operator Char const* () const { return ptr(); }
Char const* operator<<( ToPointer ) { return ptr(); }
template< class Type >
StringBuilder& operator<<( Type const& v )
{
s_ += fastStringFrom( v );
return *this;
}
};
namespace narrow {
typedef StringBuilder<char> S;
} // namespace narrow
namespace wide {
typedef StringBuilder<wchar_t> S;
} // namespace wide
然后你可以写出高效清晰的东西,比如…
using narrow::S;
std::string a = S() << "The answer is " << 6*7;
foo( S() << "Hi, " << username << "!" );
- 在while循环中输入带有std::cin的字符串后,控制台会输出大量胡言乱语
- 如何在C++中用std::cout正确显示带十六进制的字符串文本
- 使用std::mt19937从字符串中返回一个随机单词
- 我可以重新分配/覆盖std::字符串吗
- 使用自定义比较函数使用std::sort()对矢量字符串进行排序时出现问题
- 如何将这个std::字符串转换为std::基本字符串
- 字符串化递归的"std::vector<std::vector<...>>"而不使用部分模板函数专用化
- std::字符串与 PCWSTR 的对话
- 确切地说,如何解释 std::getline(stream, string) 函数在C++中填充的字符串
- 将C++ std::string 转换为 UTF-16-LE 编码的字符串
- 使用 std::string_view 的子字符串控制台输出
- std::regex:匹配由数字和空格组成的字符串,并提取数字.如何?
- 重载 std::字符串运算符+ 用于打印枚举名称
- 从std::字符串创建OssBitString
- C++ TCP 套接字 | 无法将"std::__cxx11::字符串"转换为"const
- C 中的特殊字符的子字符串(STD :: substr)
- 是否建议将字符串std::移动到将被覆盖的容器中
- 显式指定要写入的字符串 std::ostringstream
- 如何将uint16_t转换为宽字符串(std::wstring)
- 读取和Huffman压缩4字节二进制字符串STD C++Linux环境