Строка GNU STL: здесь используется copy-on-write?

(Отказ от ответственности: я не знаю, что может сказать об этом стандарт С++. Я знаю, я ужасен)

во время работы на очень больших строках я заметил, что 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!
    }
}

После того, как я немного разобрался в своем открытии, я узнал, что чтение (с оператором []) из базовой строки также занимает 0,3 секунды для всей игрушечной программы. Я не на 100% комфортно с этим. Строки STL действительно копируются на чтение, или они позволяют копировать на запись вообще? Я убежден, что оператор [] имеет некоторые гарантии против того, кто сохранит ссылку, которую он вернет, а затем напишет на нее; это действительно так? Если нет, что на самом деле происходит? Если кто-то может указать на какой-то соответствующий раздел в стандарте С++, это также будет оценено.

Для справки, я использую g++ (Ubuntu 4.4.3-4ubuntu5) 4.4.3 и GNU STL.

Ответы

Ответ 1

С++ не различает operator[] для чтения и записи, а только operator[] для объекта const и изменчивого (не const const) объекта. Поскольку a_copy является изменяемым, выбирается изменяемый operator[], который заставляет копировать, потому что этот оператор возвращает (изменяемую) ссылку.

Если эффективность является проблемой, вы можете применить a_copy к const string, чтобы принудительно использовать версию const operator[], которая не будет делать копию внутреннего буфера.

char f = static_cast<const string>(a_copy)[99];

Ответ 2

Стандарт С++ не запрещает или не предусматривает копирование на запись или любые другие детали реализации для 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];

Ответ 3

Попробуйте этот код:

#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

Что бы показалось, что да, они копируются при чтении в этом случае.