GNU STL字符串:这里是否包含copy-on-write?

时间:2010-11-01 08:06:07

标签: c++ string g++ internals

(免责声明:我不知道C ++标准对此有何看法......我知道,我很可怕)

在非常大的字符串上操作时,我注意到std :: string正在使用copy-on-write。我设法编写了可以重现观察到的行为的最小循环,例如,下面的循环运行得非常快:

#include <string>
using std::string;
int main(void) {
    string basestr(1024 * 1024 * 10, 'A');
    for (int i = 0; i < 100; i++) {
        string a_copy = basestr;
    }
}

在循环体a_copy[1] = 'B';中添加写入时,显然发生了一个实际的副本,程序以0.3秒而不是几毫秒的速度运行。 100次写入减慢了约100次。

然而它变得奇怪了。我的一些字符串没有被写入,只能读取,而这并没有反映在执行时间中,这几乎与字符串上的操作数成正比。通过一些挖掘,我发现只是从字符串读取仍然给我性能命中,所以它让我假设GNU STL字符串使用copy-on-read(?)。

#include <string>
using std::string;
int main(void) {
    string basestr(1024 * 1024 * 10, 'A');
    for (int i = 0; i < 100; i++) {
        string a_copy = basestr;
        a_copy[99]; // this also ran in 0.3s!
    }
}

在我的发现中沉淀了一段时间之后,我发现从基础字符串中读取(带有operator [])对于整个玩具程序也需要0.3秒。我对此并不是100%舒服。 STL字符串是否确实是可读写的,或者它们是否允许写入时复制?我被认为operator []有一些保护措施可以防止一个人保留它返回的引用并稍后写入它;这是真的吗?如果没有,真正发生了什么?如果有人可以指出C ++标准中的某些相关部分,那也值得赞赏。

作为参考,我使用g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3和GNU STL。

3 个答案:

答案 0 :(得分:14)

C ++不区分用于读写的operator[],而只区分const对象和可变(非const)对象的operator[]。由于a_copy是可变的,因此将选择可变operator[],这会强制复制,因为该运算符返回(可变)引用。

如果要考虑效率问题,可以将a_copy强制转换为const string以强制使用const operator[]版本的char f = static_cast<const string>(a_copy)[99]; 内部缓冲区的副本。

{{1}}

答案 1 :(得分:13)

C ++标准不禁止或强制要求{-1}}的写时复制或任何其他实现细节。只要满足语义和复杂性要求,实现就可以选择它喜欢的任何实现策略。

请注意,非std::string字符串上的operator[]实际上是一个“写”操作,因为它返回一个引用,该引用可用于在任何点修改字符串,直到下一个变异的操作字符串。这种修改不应影响任何副本。

您是否尝试过分析其中一个?

const

或者

const string a_copy = basestr;
a_copy[99];

答案 2 :(得分:2)

试试这段代码:

#include <iostream>
#include <iomanip>
#include <string>

using namespace std;

template<typename T>
void dump(std::ostream & ostr, const T & val)
{
    const unsigned char * cp = reinterpret_cast<const unsigned char *>(&val);
    for(int i=0; i<sizeof(T); i++)
        ostr
            << setw(2) << setfill('0') << hex << (int)cp[i] << ' ';
    ostr << endl;
}

int main(void) {
    string a = "hello world";
    string b = a;
    dump(cout,a);
    dump(cout,b);

    char c = b[0];

    dump(cout,a);
    dump(cout,b);
}

在GCC上,这是我得到的输出:

3c 10 51 00
3c 10 51 00
3c 10 51 00
5c 10 51 00

这似乎表明是的,在这种情况下,它们是读取的副本。