让我们考虑一下这个片段,请假设a,b,c和d是非空字符串。
std::string a, b, c, d;
d = a + b + c;
在计算这3个std::string
实例的总和时,标准库实现会创建第一个临时std::string
对象,在其内部缓冲区中复制a
和{{1}的连接缓冲区然后,在临时字符串和b
之间执行相同的操作。
同事程序员强调,c
可以定义为operator+(std::string, std::string)
而不是此行为。
这个对象的作用是将实际的连接推迟到它被转换为std::string_helper
的那一刻。显然,std::string
将被定义为返回相同的帮助器,这将“记住”它有一个额外的串联来执行。
这样的行为可以节省创建n-1临时对象,分配缓冲区,复制它们等的CPU成本。所以我的问题是:为什么它不能像那样工作?我想不出任何缺点或限制。
答案 0 :(得分:6)
为什么它不能像那样工作?
我只能推测为什么它最初是这样设计的。也许字符串库的设计者根本没有想到它;也许他们认为额外的类型转换(见下文)可能会使某些情况下的行为过于令人惊讶。它是最古老的C ++库之一,我们认为理所当然的许多智慧在过去几十年中根本不存在。
至于为什么它没有被改变为这样工作:它可能通过添加额外的用户定义类型转换来破坏现有代码。隐式转换最多只能涉及一个用户定义的转换。这由C ++ 11,13.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以不同的方式修复了低效率。
答案 1 :(得分:6)
显而易见的答案:因为标准不允许。它在某些情况下通过引入额外的用户定义转换来影响代码:如果C
是一个类型,其中用户定义的构造函数采用std::string
,那么它将使:
C obj = stringA + stringB;
非法的。
答案 2 :(得分:4)
取决于。
在C ++ 03中,确实可能存在轻微的低效率(与Java和C#相比,因为它们使用字符串实习)。这可以通过以下方式得到缓解:
d = std::string("") += a += b +=c;
这不是真的...惯用。
在C ++ 11中,operator+
被重载为rvalue引用。意思是:
d = a + b + c;
转变为:
d.assign(std::move(operator+(a, b).append(c)));
(几乎)尽可能高效。
C ++ 11版本中唯一的低效率是内存不是一开始就保留的,所以可能会重新分配并最多复制2次(对于每个新字符串)。仍然,因为追加是摊销O(1),除非C比B长,否则最坏的情况应该发生一次重新分配+复制。当然,我们在这里谈论POD复制(所以memcpy
电话)。
答案 3 :(得分:2)
听起来像我这样的事情已经存在:std::stringstream
。
只有<<
代替+
。仅仅因为std::string::operator +
存在,它并不是最有效的选择。
答案 4 :(得分:0)
我认为如果你使用+=
,那么它会快一点:
d += a;
d += b;
d += c;
它应该更快,因为它不会创建临时对象。或者只是这个,
d.append(a).append(b).append(c); //same as above: i.e using '+=' 3 times.
答案 5 :(得分:0)
不进行单个+
串联串联的主要原因,特别是在循环中没有这样做,就是有O( n 2 )复杂性。
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 << "!" );