14

(免责声明:我不知道 C++ 标准对此会说什么......我知道,我很可怕)

在处理非常大的字符串时,我注意到 std::string 正在使用写时复制。我设法编写了最小的循环来重现观察到的行为,例如,下面的循环运行得非常快:

#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 字符串正在使用读取时复制(?)。

#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。

4

3 回答 3

14

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

如果效率是一个问题,您可以将a_copyaconst string强制转换为要使用的const版本operator[],这不会复制内部缓冲区。

char f = static_cast<const string>(a_copy)[99];
于 2010-11-01T08:11:59.227 回答
13

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

请注意,operator[]在非const字符串上实际上是一个“写入”操作,因为它返回一个引用,该引用可用于在任何时候修改字符串,直到对字符串进行变异的下一个操作。此类修改不应影响任何副本。

您是否尝试过分析这两者之一?

const string a_copy = basestr;
a_copy[99];

或者

string a_copy = basestr;
const std::string& a_copy_ref = a_copy;
a_copy_ref[99];
于 2010-11-01T08:14:49.787 回答
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

这似乎表明是的,在这种情况下它们是读取时复制的。

于 2010-11-01T09:07:57.770 回答